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

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

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

管理使用自定义布局的数据项,并提出他们的有序集合的对象。

他们可以是非常简单的,只要你代表以同样的方式您的数据。UICollectionViewDataSourceUICollectionViewDelegate复杂性的增加,当我们注册多个UICollectionViewCell,这就需要以自己独特的方式来进行配置。如果你已经与iOS工作了一段时间,我敢肯定你亲眼目睹可怕的()的实现FUNC的CollectionView(_的CollectionView:UICollectionView,cellForItemAt indexPath:IndexPath) - > UICollectionViewCell

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

用户供稿

在我们的示例项目,我们将显示用户与广告一起的饲料。这是一个相当常见的场景,不少开发商目前面临。


在饲料中显示的内容的异构是以下情况:

  • 普通用户将使用显示快照细胞(见绕口令)。它有一个背景图片,头像和用户名的标签。
  • 推荐用户将使用显示细胞(见书呆子)。它只有一个头像和用户名的标签。
  • 广告将与我们的用户交织在一起。对于MVP,我们只是要包括图片广告

让我们开始工作☺️!

去耦单元

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

命令模式

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

这种模式的关键是简化我们的单元配置,取消或任何其他特定要求。

协议CollectionViewCellCommand {FUNC执行(单元:UICollectionViewCell)}

鉴于UICollectionViewCells为可重复使用的,命令需要采取在细胞中作为参数。

视图模型

CollectionViewCellViewModel包含所有需要离队,显示和互动与我们的细胞的信息。

结构CollectionViewCellViewModel {让标识符:字符串让尺寸:CGSize让命令:[CollectionViewCellCommandKey:CollectionViewCellCommand]}枚举CollectionViewCellCommandKey {壳体结构的情况下消除病例选择//可以添加更多的情况下,以取消选择把手或任何其它相互作用}

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

异构数据的条件分支语句的主要驱动力,因为每一种情况下需要将因业务需求或只是单纯的数据不兼容不同的处理。CollectionViewCellViewModel成为事实上的标准表示法任何数据要么行为需要通过在视觉上表示UICollectionView

如何使用命令:

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

配置命令

识别码CollectionViewCellModel用于出列从适当的细胞UICollectionView。从视图模型的特定命令可以通过使用拾取CollectionViewCellCommandKey在这种情况下,。组态键。

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

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

选择命令

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

FUNC的CollectionView(_的CollectionView:UICollectionView,didSelectItemAt indexPath:IndexPath){后卫让细胞= collectionView.cellForItem(在:indexPath)否则{返回}让视图模型=的ViewModels [indexPath.item] viewModel.commands [.selection] ?.执行(细胞:细胞)}

宏命令

在示例项目中,当用户单元被选中,我们正在推动一个视图控制器。如果我们也想跟踪分析事件,我们可以把它一起在一个单一的命令,但将打破[单一职责原则(https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)。为了避免这一缺陷,我们可以创建一个宏命令包含多个命令的数组。

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

用户快照细胞

一种作为理想的抽象来创建一个视图模型我们快照细胞。在这里面,我们可以指定尺寸识别码细胞以及任何的命令

结构UserSnapshotCollectionViewCellViewModelFactory {FUNC创建(用户:用户) - > CollectionViewCellViewModel {让大小= CGSize(宽度:300,高度:200)让configuration命令= UserSnapshotCollectionViewCellConfigurationCommand(用户:用户,imageNetworkManager:ImageNetworkManager())让命令:[CollectionViewCellCommandKey:CollectionViewCellCommand]= [.configuration:configuration命令//附加(command键,命令)键 - 值对可以在这里加入到地址不同的情况(选择,取消选择,等等)]返回CollectionViewCellViewModel(标识符:“UserSnapshotCollectionViewCell”,大小:大小,命令:命令)}}

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

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

结构UserSnapshotCollectionViewCellConfigurationCommand:CollectionViewCellCommand {//内部状态所需执行的命令私人提醒用户:用户私人设imageNetworkManager:ImageNetworkManagerProtocol的init(用户:用户,imageNetworkManager:ImageNetworkManagerProtocol = ImageNetworkManager()){self.user =用户self.imageNetworkManager = imageNetworkManager}// This is where the magic happens  func perform(cell: UICollectionViewCell) { guard let cell = cell as? UserSnapshotCollectionViewCell else { return } cell.usernameLabel.text = user.username _ = imageNetworkManager.request(url: user.avatarUrl) { (image) in cell.avatarImageView.image = image } if let backgroundUrl = user.backgroundUrl { _ = imageNetworkManager.request(url: backgroundUrl) { (image) in cell.backgroundImageView.image = image } } } }

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

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

类UsersViewController:UIViewController中,UICollectionViewDataSource,UICollectionViewDelegateFlowLayout {@IBOutlet弱VAR的CollectionView:UICollectionView!{didSet {collectionView.delegate =自collectionView.dataSource =自}}变种的ViewModels = [CollectionViewCellViewModel]()重写FUNC viewDidLoad中(){super.viewDidLoad()setupViewModels()} FUNC setupViewModels(){让userFactory = UserSnapshotCollectionViewCellViewModelFactory()让userViewModels = User.all.map(userFactory.create)viewModels.append(contentsOf:userViewModels)}

广告细胞

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

结构广告{让ID:诠释让的contentURL:URL让clickthroughUrl:URL让类型:AdvertisementType //测试数据的静止无功一切:[广告] {返回[广告(ID:1的contentURL:URL(字符串:“https://开头cdn.okccdn.com/media/img/hub/mediakit/188bet金宝搏官网okcupid_darkbg.png“)!clickthroughUrl:URL(字符串: ”https://okcupid.com/home“)!类型:图像配)]}}枚举AdvertisementType {情况下图像的情况下视频场合的声音}

该图像广告将被在视觉上表示AdvertisementCollectionViewCell,这只是包括一个的UIImageView。当我们点击这个单元格,点击网址将被打开的UIApplication。该SelectionCommand要求的UIApplication广告执行。

进口UIKit的STRUCT AdvertisementCollectionViewCellSelectionCommand:CollectionViewCellCommand {私人设应用:UIApplication的私人让广告:广告的init如果应用{(广告:广告,应用程序::UIApplication的){self.application =应用self.advertisement =广告} FUNC执行(UICollectionViewCell小区)。canOpenURL(advertisement.clickthroughUrl){application.open(advertisement.clickthroughUrl,选项:[:],completionHandler:无)}}}

AdvertisementCollectionViewCellViewModelFactory负责的转型广告CollectionViewCellViewModel。只是在想用户- >视图模型情况下,该工厂定义尺寸识别码并支持命令

结构AdvertisementCollectionViewCellViewModelFactory {FUNC创建(广告:广告,应用程序:UIApplication的) - > CollectionViewCellViewModel {让大小= CGSize(宽度:220,高度:220)让configuration命令= AdvertisementCollectionViewCellConfigurationCommand(广告:广告,imageNetworkManager:ImageNetworkManager())让selectionCommand = AdvertisementCollectionViewCellSelectionCommand(广告:广告,应用程序:应用程序)让命令:[CollectionViewCellCommandKey:CollectionViewCellCommand] = [.configuration:configuration命令,.selection:selectionCommand]返回CollectionViewCellViewModel(标识符: “AdvertisementCollectionViewCell”,大小:大小,命令:命令)}}

为了将这些改变成UsersViewController,我们可以通过修改setupViewModels()功能通过添加几行:

让advertisementFactory = AdvertisementCollectionViewCellViewModelFactory()让广告= Advertisement.all.map {advertisementFactory.create(广告:$ 0,应用程序:.shared)} viewModels.insert(contentsOf:广告,在:viewModels.count / 2)

结论

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

正如你可能已经注意到,添加不同类型的细胞几乎没有修改的UIViewController。该命令模式也开启了大门为我们创造可撤销的操作,它可以来非常方便,如果集合视图具有细胞修改输入字段支持结算挂起更改。

瓶盖正变得越来越流行,和函数式编程采用比以往任何时候都高。但是,命令是一个面向对象的替代回调,它们是可以被操纵和扩展的第一类对象。

这是什么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点击这里了解更多