我应该在 UITableViewCell 中使用动态堆栈视图还是使用 UICollectionView?
Posted
技术标签:
【中文标题】我应该在 UITableViewCell 中使用动态堆栈视图还是使用 UICollectionView?【英文标题】:Should I use dynamic stackview inside UITableViewCell or use UICollectionView? 【发布时间】:2020-10-09 09:12:48 【问题描述】:你好。我正在构建具有复杂 UITableViewCell 的航班预订应用程序。基本上,它是带有阴影的简单卡片,有一堆堆栈视图。第一个stackview,您在图像上看到它是用于标签的。它是水平的和动态的。下一个 stackview 显示航班。它具有复杂的自定义视图,但为了简单起见,它以绿色边框显示。它也是动态的,所以我需要单独的stackview。下一个堆栈视图适用于可以处理此预订的航空公司。我称他们为操作员。它也是动态的,所以我为它们构建了另一个 stackview。所有这些堆栈视图都在一些核心堆栈视图中。你可能会问,为什么我创建了单独的堆栈视图而不是一个?因为,上面的标签可以隐藏。而且所有堆栈视图中的间距也不同。
这是一个非常复杂的设计。我遵循上述方法并构建 UITableViewCell。但是性能真的很差。原因很简单:我在 cellForRowAt 中做了太多的事情。每次出列单元格时都会调用 UITableViewCell 的configure
方法。这意味着我应该每次都清理我的堆栈视图,然后才附加我的视图。我认为这确实会影响性能。我不告诉单元格内的其他 if/else 语句。第一个问题是在这种情况下如何提高 UITableViewCell 的滚动性能?
一些开发者认为 UITableView 应该被杀掉。 UICollectionView 统治着世界。好的,但是我可以在这种设计中使用 UICollectionView 吗?是的,当然,但上面的卡片将是一个 UICollectionViewCell,我根本不会避免问题。另一种解决方案是为标签(参见图片)、航班和运营商构建单独的 UICollectionViewCell。这肯定会提高性能。但是,我怎样才能让它们都住在卡片里呢?
附:我的 cellForRowAt 方法里面有什么?只有一个 configure
方法和为闭包赋值。但是配置方法相当复杂。它得到一些具有大量计算属性的协议。我将该协议的实现传递给configure
方法。协议是这样的:
protocol Booking
var flights: [Flight] get
var operators: [Operator] get
var labels: [Label] get
var isExpanded: Bool get set
该协议的实施也很复杂。有一堆映射函数和 if/else 语句。一些字符串操作。那么,这会导致问题吗?我该如何解决?通过避免计算属性而只将属性(航班、运算符)传递给实现?
【问题讨论】:
如果不显示您的细胞有多“复杂”,就很难提供帮助。一种可能性:不是每次都删除/重新添加子视图,而是只删除额外的子视图或添加新的子视图。此外,如果您将拥有最多 10 个“操作员”,您可以在实例化单元格时创建 10 个操作员标签(或视图,无论它们是什么),然后根据需要显示或隐藏。而且,根据您认为非常糟糕的性能,您可能需要重新考虑您的设计方法。 我在单元格中有配置方法,例如,我在其中传递航班数组、运算符数组。然后我将它们添加到stackview。那么,我怎么知道哪些已经添加了呢?检查排列的子视图长度?但是 UITableView 调用 cellForRowAt 来重用单元格,所以我想如果我检查长度,一些视图会在那里清空。 【参考方案1】:正如我在评论中所说,没有看到完整的细节,很难提供帮助。而且,这是一个相当广泛的问题。
不过,这可能会给您一些帮助...
考虑两个细胞类别。在每个单元格中,“基本”元素都是在创建单元格时添加的——无论单元格的实际数据如何,这些元素都会存在:
您的“主要”堆栈视图 您的“标签”堆栈视图 您的“航班”堆栈视图 您的“操作员”堆栈视图为简化起见,让我们考虑一下“运算符”堆栈视图,我们会说每个“行”都是一个标签。
当您在单元格中设置数据时,您现在可能正在做的事情是这样的......
在单元格的初始化函数中:
// create your main and 3 sub-stackViews
那么,当你从cellForRowAt
设置数据时:
// remove all labels from operator stack
operatorStack.arrangedSubviews.forEach
$0.removeFromSuperview()
// add new label for each operator
thisBooking.operators.forEach op in
let v = UILabel()
v.font = .systemFont(ofSize: 15)
v.text = op.name
operatorStack.addArrangedSubview(v)
因此,每次您将cellForRowAt
中的单元格出列并设置其数据时,您都会从堆栈视图中删除所有“操作员”视图,然后重新创建并重新添加它们。
相反,如果您知道它最多有 10 个“操作员”子视图,您可以在创建单元格时添加它们,然后根据需要显示/隐藏。
在单元格的初始化函数中:
// create your main and 3 sub-stackViews
// add 10 labels to operator stack
// when cell is created
for _ in 1...10
let v = UILabel()
v.font = .systemFont(ofSize: 15)
operatorStack.addArrangedSubview(v)
那么,当你从cellForRowAt
设置数据时:
// set all labels in operator stack to hidden
operatorStack.arrangedSubviews.forEach
$0.isHidden = true
// fill and unhide labels as needed
for (op, v) in zip(thisBooking.operators, operatorStack.arrangedSubviews)
guard let label = v as? UILabel else fatalError("Setup was wrong!")
label.text = op.name
label.isHidden = false
这样,我们只创建和添加一次“操作员视图” - 在创建单元格时。当它出列/重用时,我们只是隐藏了未使用的视图。
再一次,既然你说你有一个“非常复杂的设计”,还有很多事情需要考虑......正如我提到的,你可能需要重新考虑你的整个方法。
但是,基本思想是只创建和添加一次子视图,然后在重复使用单元格时根据需要显示/隐藏它们。
【讨论】:
情况是不知道能从服务器得到多少算子。项目的要求是它应该是动态的。具体数量不详。那么,您将如何处理呢? @neo - 再说一次,不知道你的整个项目,很难说。应用程序设计不仅仅是“我需要显示所有这些数据”。也许你试图同时展示太多东西——你还需要考虑用户想要看到什么。举个例子(与您正在做的事情没有直接关系):假设我想显示数据库中的 5,000 条记录?有人想滚动一个 5,000 行的表格吗?可能不是。所以...如果您的“单元格”可能有 100 个“操作员”,您认为用户想要滚动所有这些操作符只是为了到达下一行吗?以上是关于我应该在 UITableViewCell 中使用动态堆栈视图还是使用 UICollectionView?的主要内容,如果未能解决你的问题,请参考以下文章
UITableViewCell 中单个 UIImageView 应该都有哪些约束?