使用自动布局约束动态调整 Table 视图单元格的大小
Dynamically size Table View Cells using Auto Layout constraints
更新
根据我的最新发现,我已经完全修改了这个问题。
目标
我的目标是实现如下效果:
- 有一个简单的table视图
- 用户选择了一行
- 所选行展开,显示原始标签下方的另一个标签
请注意,我知道,这可以通过所选单元格 I already have a successful implementation using that method 下面的 inserting/deleting 个单元格来实现。
这一次,我想尝试使用自动布局约束来实现这一点。
当前状态
我有一个sample project available for anyone to check, and also opened an issue。总而言之,这是我迄今为止尝试过的方法:
我作为这里的演员有以下看法:
- 单元格的内容视图
- 俯视图,包含主标签 ("main view")
- 底部视图,位于主视图下方,包含最初隐藏的标签 ("detail view")
我已经按照以下方式在我的单元格中设置了自动布局约束(请注意,这完全是伪语言):
- mainView.top = contentView.top
- mainView.leading = contentView.leading
- mainView.trailing = contentView.trailing
- mainView.bottom = detailView.top
- detailView.leading = contentView.leading
- detailView.trailing = contentView.trailing
- detailView.bottom = contentView.bottom
- detailView.height = 0
我有一个自定义的 UITableViewCell
子类,有多个插座,但这里最重要的是前面提到的高度限制的插座:这里的想法是将其 constant
设置为 0默认,但当单元格被选中时,将其设置为 44,使其可见:
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
detailViewHeightConstraint.constant = selected ? detailViewDefaultHeight : 0
UIView.animateWithDuration(0.3) {
self.layoutIfNeeded()
}
}
我得到以下结果:
所以效果是可以的,但不是我最初想象的那样。我不希望将主视图向上推,而是希望在显示详细视图时单元格的高度 增长 ,而在隐藏时 收缩 。
我在运行时检查了我的布局层次结构:
- 初始状态正常。内容视图的高度等于我的主视图的高度(在本例中,它是 125 点)。
- 选中单元格时,详细视图的高度约束增加到 44 点,并且两个视图正确堆叠vertically.But而不是单元格的内容视图扩展,而是主视图缩小.
问题
我需要的是:table 视图单元格内容视图的高度应等于
- 主视图的高度,当详细视图的高度限制为 0 时(目前有效)
- 当详细视图的高度约束设置正确时主视图高度+详细视图高度(这不起作用)。
我必须如何设置约束才能实现该目标?
这是在 obj-c 中,但我相信你会处理的:
加入你的viewDidLoad:
self.tableView.estimatedRowHeight = self.tableView.rowHeight;
self.tableView.rowHeight = UITableViewAutomaticDimension;
这将为您的 tableView 启用自动调整单元格大小,并且应该在 iOS8+
上工作
经过大量研究,我想我已经在 this great article.
的帮助下找到了解决方案
调整单元格大小所需的步骤如下:
在主视图和详细视图中,我最初将标签设置为水平和垂直居中。这对于自我调整大小的单元格来说是不够的。我需要做的第一件事是使用垂直间距约束而不是简单对齐来设置布局:
此外,您应该将 Main Container 的垂直压缩阻力设置为 1000。
细节视图有点棘手:除了创建适当的垂直约束外,您还必须调整它们的优先级以达到预期效果:
- Detail Container 的高度限制为 44 点,但要使其可选,请将其优先级设置为 999(根据文档,任何低于 "Required" 的值都将被视为如此)。
- 在 Detail Container 中,设置垂直间距约束,并赋予它们 998 的优先级。
主要思想如下:
- 默认情况下,单元格是折叠的。为此,我们必须以编程方式将 Detail Container 的高度约束常量设置为 0。由于其优先级高于单元格内容视图中的垂直约束,后者将被忽略,因此 Detail Container 将被隐藏。
- 当我们 select 单元格时,我们希望它展开。这意味着,必须控制垂直约束:我们将优先级详细信息容器的高度约束设置为较低的值(我使用 250),因此它将被忽略以支持内容视图中的约束。
我不得不修改我的 UITableViewCell
子类以支持这些操作:
// `showDetails` is exposed to control, whether the cell should be expanded
var showsDetails = false {
didSet {
detailViewHeightConstraint.priority = showsDetails ? lowLayoutPriority : highLayoutPriority
}
}
override func awakeFromNib() {
super.awakeFromNib()
detailViewHeightConstraint.constant = 0
}
要触发该行为,我们必须覆盖 tableView(_:didSelectRowAtIndexPath:)
:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: false)
switch expandedIndexPath {
case .Some(_) where expandedIndexPath == indexPath:
expandedIndexPath = nil
case .Some(let expandedIndex) where expandedIndex != indexPath:
expandedIndexPath = nil
self.tableView(tableView, didSelectRowAtIndexPath: indexPath)
default:
expandedIndexPath = indexPath
}
}
请注意,我引入了 expandedIndexPath
来跟踪我们当前扩展的索引:
var expandedIndexPath: NSIndexPath? {
didSet {
switch expandedIndexPath {
case .Some(let index):
tableView.reloadRowsAtIndexPaths([index], withRowAnimation: UITableViewRowAnimation.Automatic)
case .None:
tableView.reloadRowsAtIndexPaths([oldValue!], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
}
设置 属性 将导致 table 视图重新加载适当的索引,给我们一个绝佳的机会来告诉单元格,如果它应该展开:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! ExpandableTableViewCell
cell.mainTitle = viewModel.mainTitleForRow(indexPath.row)
cell.detailTitle = viewModel.detailTitleForRow(indexPath.row)
switch expandedIndexPath {
case .Some(let expandedIndexPath) where expandedIndexPath == indexPath:
cell.showsDetails = true
default:
cell.showsDetails = false
}
return cell
}
最后一步是在 viewDidLoad()
中启用自动调整大小:
override func viewDidLoad() {
super.viewDidLoad()
tableView.contentInset.top = statusbarHeight
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 125
}
结果如下:
单元格现在可以正确调整自己的大小。您可能会注意到动画仍然有点奇怪,但修复它不属于本题范围。
结论:这比它应该做的要难得多。我真的希望在未来看到一些改进。
更新
根据我的最新发现,我已经完全修改了这个问题。
目标
我的目标是实现如下效果:
- 有一个简单的table视图
- 用户选择了一行
- 所选行展开,显示原始标签下方的另一个标签
请注意,我知道,这可以通过所选单元格 I already have a successful implementation using that method 下面的 inserting/deleting 个单元格来实现。
这一次,我想尝试使用自动布局约束来实现这一点。
当前状态
我有一个sample project available for anyone to check, and also opened an issue。总而言之,这是我迄今为止尝试过的方法:
我作为这里的演员有以下看法:
- 单元格的内容视图
- 俯视图,包含主标签 ("main view")
- 底部视图,位于主视图下方,包含最初隐藏的标签 ("detail view")
我已经按照以下方式在我的单元格中设置了自动布局约束(请注意,这完全是伪语言):
- mainView.top = contentView.top
- mainView.leading = contentView.leading
- mainView.trailing = contentView.trailing
- mainView.bottom = detailView.top
- detailView.leading = contentView.leading
- detailView.trailing = contentView.trailing
- detailView.bottom = contentView.bottom
- detailView.height = 0
我有一个自定义的 UITableViewCell
子类,有多个插座,但这里最重要的是前面提到的高度限制的插座:这里的想法是将其 constant
设置为 0默认,但当单元格被选中时,将其设置为 44,使其可见:
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
detailViewHeightConstraint.constant = selected ? detailViewDefaultHeight : 0
UIView.animateWithDuration(0.3) {
self.layoutIfNeeded()
}
}
我得到以下结果:
所以效果是可以的,但不是我最初想象的那样。我不希望将主视图向上推,而是希望在显示详细视图时单元格的高度 增长 ,而在隐藏时 收缩 。
我在运行时检查了我的布局层次结构:
- 初始状态正常。内容视图的高度等于我的主视图的高度(在本例中,它是 125 点)。
- 选中单元格时,详细视图的高度约束增加到 44 点,并且两个视图正确堆叠vertically.But而不是单元格的内容视图扩展,而是主视图缩小.
问题
我需要的是:table 视图单元格内容视图的高度应等于
- 主视图的高度,当详细视图的高度限制为 0 时(目前有效)
- 当详细视图的高度约束设置正确时主视图高度+详细视图高度(这不起作用)。
我必须如何设置约束才能实现该目标?
这是在 obj-c 中,但我相信你会处理的:
加入你的viewDidLoad:
self.tableView.estimatedRowHeight = self.tableView.rowHeight;
self.tableView.rowHeight = UITableViewAutomaticDimension;
这将为您的 tableView 启用自动调整单元格大小,并且应该在 iOS8+
上工作经过大量研究,我想我已经在 this great article.
的帮助下找到了解决方案调整单元格大小所需的步骤如下:
在主视图和详细视图中,我最初将标签设置为水平和垂直居中。这对于自我调整大小的单元格来说是不够的。我需要做的第一件事是使用垂直间距约束而不是简单对齐来设置布局:
此外,您应该将 Main Container 的垂直压缩阻力设置为 1000。
细节视图有点棘手:除了创建适当的垂直约束外,您还必须调整它们的优先级以达到预期效果:
- Detail Container 的高度限制为 44 点,但要使其可选,请将其优先级设置为 999(根据文档,任何低于 "Required" 的值都将被视为如此)。
- 在 Detail Container 中,设置垂直间距约束,并赋予它们 998 的优先级。
主要思想如下:
- 默认情况下,单元格是折叠的。为此,我们必须以编程方式将 Detail Container 的高度约束常量设置为 0。由于其优先级高于单元格内容视图中的垂直约束,后者将被忽略,因此 Detail Container 将被隐藏。
- 当我们 select 单元格时,我们希望它展开。这意味着,必须控制垂直约束:我们将优先级详细信息容器的高度约束设置为较低的值(我使用 250),因此它将被忽略以支持内容视图中的约束。
我不得不修改我的 UITableViewCell
子类以支持这些操作:
// `showDetails` is exposed to control, whether the cell should be expanded
var showsDetails = false {
didSet {
detailViewHeightConstraint.priority = showsDetails ? lowLayoutPriority : highLayoutPriority
}
}
override func awakeFromNib() {
super.awakeFromNib()
detailViewHeightConstraint.constant = 0
}
要触发该行为,我们必须覆盖 tableView(_:didSelectRowAtIndexPath:)
:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: false)
switch expandedIndexPath {
case .Some(_) where expandedIndexPath == indexPath:
expandedIndexPath = nil
case .Some(let expandedIndex) where expandedIndex != indexPath:
expandedIndexPath = nil
self.tableView(tableView, didSelectRowAtIndexPath: indexPath)
default:
expandedIndexPath = indexPath
}
}
请注意,我引入了 expandedIndexPath
来跟踪我们当前扩展的索引:
var expandedIndexPath: NSIndexPath? {
didSet {
switch expandedIndexPath {
case .Some(let index):
tableView.reloadRowsAtIndexPaths([index], withRowAnimation: UITableViewRowAnimation.Automatic)
case .None:
tableView.reloadRowsAtIndexPaths([oldValue!], withRowAnimation: UITableViewRowAnimation.Automatic)
}
}
}
设置 属性 将导致 table 视图重新加载适当的索引,给我们一个绝佳的机会来告诉单元格,如果它应该展开:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! ExpandableTableViewCell
cell.mainTitle = viewModel.mainTitleForRow(indexPath.row)
cell.detailTitle = viewModel.detailTitleForRow(indexPath.row)
switch expandedIndexPath {
case .Some(let expandedIndexPath) where expandedIndexPath == indexPath:
cell.showsDetails = true
default:
cell.showsDetails = false
}
return cell
}
最后一步是在 viewDidLoad()
中启用自动调整大小:
override func viewDidLoad() {
super.viewDidLoad()
tableView.contentInset.top = statusbarHeight
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 125
}
结果如下:
单元格现在可以正确调整自己的大小。您可能会注意到动画仍然有点奇怪,但修复它不属于本题范围。
结论:这比它应该做的要难得多。我真的希望在未来看到一些改进。