如何使用协议构建具有多种单元格类型的TableView

了解UITableViews(和UICollectionViews)作为iOS开发人员的工作方式就像了解盐和胡椒如何做饭一样。 它们是大多数受欢迎的应用程序中的一部分,尽管使用起来相对简单,但我们可以对其实现进行真正的复杂化。


由于在UIKit中使用了协议,因此实现简单的UITableView可以非常简单。 我们需要将数据连接到UITableVIew的主要协议是:UITableViewDataSource和UITableViewDelegate。 但是,这意味着我们的ViewController符合这些协议是什么意思? (他们为什么被任命为代表?)

有趣的是,这个词在我们考虑时会有所解释。 符合协议的东西就是满足协议要求的东西,有点像诺言。 因此,实质上,当我们说ViewController符合UITableView协议时,我们保证ViewController将具有指定的变量并能够执行指定的方法 。 查看UITableViewDataSource协议,我们看到:

从文档中我们可以看到,我们所需要做的就是实现两个必需的功能以满足协议。 那么代表来自哪里呢?

当我们委派某些东西时,我们将责任转移给了其他人。 例如,朋友A可以将扔垃圾的任务委托给朋友B。朋友A不一定关心朋友B如何摆脱垃圾,他们只需要知道朋友B有能力扔垃圾即可。 。

将其带回到iOS开发中,实质上,我们是在声明一种方法,该方法是我们要委派给实现职责的一种操作。 这是有道理的,因为并非所有朋友都喜欢以相同的方式扔垃圾。


当我们想让UITableViews变得复杂时,我们可以创建多个原型单元。 对于诸如FaceBook feed这样的事情,您可能具有不同的帖子类型(新照片,共享视频等),这是有意义的。 那么,当所有这些不同的单元接收不同的数据并进行不同的配置时,我们如何管理它们呢?

当我第一次遇到这个问题时,我最初的想法是在cellForRowAt方法中有一个巨大的switch语句,例如:

switch语句定义了扩展困难的最大原因。 围绕枚举构建的逻辑要求对跨多个对象共享的模型进行更改。 我们可以通过协议和一些强制转换来简化此过程。

tableView函数dequeueReusableCell具有保护语句,因为我们试图将单元格强制转换为特定类型。 这是确保单元格类型正确的安全方法,但在失败时也会使我们处于怪异状态。 如果方法dequeueReusableCell无法找到指定的单元格,则将出现fatalError。 这样做很大程度上有助于了解应用程序的状态,因为不希望返回默认单元格。

那好多了,但是我们仍然停留在那个奇怪的switch语句上。 让我们尝试使用协议删除它。

这是我们回到扔垃圾的时候。 如果我们看一下cellForRowAt方法内部的单元格在做什么,它们都是在配置。 我们可以将单元格转换为符合协议的对象吗? 我们要告诉单元执行类似的操作,更新单元的信息。

首先,这是一个棘手的例子:

ViewController

ViewController通常是UITableView的委托/数据源,dataSource是提供数据的源,在这种情况下,我们将引用CustomElementModels数组。 请注意,这是符合CustomElementModel的对象数组。

CustomElementModel协议

在这里,您可以放置​​所有模型的所有必需变量和函数。 在这里,我只是指定我希望每个模型至少告诉我它是什么类型,将类型限制为枚举:C ustomElementType。

CustomElementCell协议

同样,在这里您可以将所有必需的内容放到所有单元格中,在这种情况下,我只想确保每个符合CustomElementCell协议的对象都具有一个configure函数,该函数接受一个符合CustomElementModel的对象。


现在,我们基本上可以使用我们知道每个模型具有的类型作为变量来确定UITableView中的单元格标识符,然后将其强制转换为符合CustomElementCell的对象。

我知道你们中的一些人可能会吓坏了,我强迫很多,别担心。 我知道这很吓人,但这是可以强制施放的例子之一,这就是原因。

就像我之前说过的那样,如果dequeueReusableCell无法找到具有指定参数的单元格,则会引发致命错误。 如果我们成功地使一个单元出队,是否有任何场景可以按预期方式将其成功投射为另一个单元? 在开发中,您希望强制转换该单元格,因为您希望它是该类型 ,如果将其转换为您不期望的另一个单元格,则可以在应用程序中引入更多错误。

那最后一行呢?

 返回customCell为!  UITableViewCell 

在这种情况下,我们可以安全地强制投射,但是为什么必须强制投射? 如果回头看一下协议,我们仅有的信息就是该单元能够配置,它不记得它是一个UITableViewCell,因为我们将其强制转换为符合协议的对象。

但这并不意味着它不再是UITableViewCell。 当我们投射某种东西时,我们是在一定范围内进行投射,而实际上并没有改变它的核心。 我们可以安全地将其强制转换为UITableViewCell,因为, 我们知道到达此部分代码的唯一实例是dequeueWithIdentifier成功时 。 因此,该单元格必须是UITableViewCell。

我知道,这可能会造成混淆,或者至少在我偶然发现此解决方案时才对我来说。 以下是个人资料CustomElementType的工作方式示例:

在这里,我们可以看到ProfileElementCell通过包含协议中指定的configure函数来满足协议。 然后,我们采用符合CustomElementModel的模型,并将其转换为特定模型。

希望您喜欢我的第一篇中型文章,如果有任何疑问或反馈,请随时发表评论。

优点:您可以使用类型别名来简化和清理一些逻辑。

编辑:清理单词选择。