14

Update

I have revised the question completely after my latest findings.

Goal

My goal is to implement the following effect:

  1. There is a simple table view
  2. The user selects a row
  3. The selected row expands, revealing another label below the original one

Please note that I am aware, that this can be achieved by inserting/deleting cells below the selected one, I already have a successful implementation using that method.

This time, I want to try to achieve this using Auto Layout constraints.

Current status

I have a sample project available for anyone to check, and also opened an issue. To summarize, here's what I've tried so far:

I have the following views as actors here:

  • The cell's content view
  • A top view, containing the main label ("main view")
  • A bottom view, below the main view, containing the initially hidden label ("detail view")

I have set up Auto Layout constraints within my cell the following way (please note that this is strictly pseudo-language):

  • 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

I have a custom UITableViewCell subclass, with multiple outlets, but the most important here is an outlet for the height constraint mentioned previously: the idea here is to set its constant to 0 by default, but when the cell is selected, set it to 44, so it becomes visible:

override func setSelected(selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    detailViewHeightConstraint.constant = selected ? detailViewDefaultHeight : 0
    UIView.animateWithDuration(0.3) {
        self.layoutIfNeeded()
    }
}

I have the following result:

Current status

So the effect is working, but not exactly how I originally imagined. Instead of pushing the main view up, I want the cell's height to grow when the detail view is shown, and shrink back when it's hidden.

I have examined my layout hierarchy during runtime:

  • The initial state is OK. The height of the content view is equal to the height of my main view (in this case, it's 125 points).

Screenshot initial

  • When the cell is selected, the height constraint of the detail view is increased to 44 points and the two views are properly stacked vertically.But instead of the cell's content view extending, but instead, the main view shrinks.

Screenshot detailed

Question

What I need is the following: the height of table view cell's content view should be equal to

  • the height of the main view, when the detail view's height constraint is 0 (currently this works)
  • main view height + detail view height when the detail view's height constraint is set properly (this does not work).

How do I have to set my constraints to achieve that?

4

2 回答 2

13

经过大量研究,我想我已经在这篇很棒的文章的帮助下找到了解决方案。

以下是调整单元格大小所需的步骤:

在主视图和详细视图中,我最初将标签设置为水平和垂直居中。这对于自行调整大小的单元格是不够的。我需要做的第一件事是使用垂直间距约束而不是简单的对齐来设置我的布局:

主视图

此外,您应该将主容器的垂直压缩阻力设置为 1000。

细节视图有点棘手:除了创建适当的垂直约束之外,您还必须使用它们的优先级来达到预期的效果:

  • Detail Container 的高度被限制为 44 点,但要使其成为可选,请将其优先级设置为 999(根据文档,低于“必需”的任何内容都将被视为此类)。
  • 在 Detail Container 中,设置垂直间距约束,并赋予它们 998 的优先级。

详细视图

主要思想如下:

  • 默认情况下,单元格是折叠的。为此,我们必须以编程方式将 Detail Container 的高度约束的常量设置为 0。由于它的优先级高于单元格内容视图中的垂直约束,后者将被忽略,因此 Detail Container 将被隐藏。
  • 当我们选择单元格时,我们希望它展开。这意味着,必须控制垂直约束:我们将优先级 Detail Container 的高度约束设置为较低的值(我使用了 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)
        }
    }
}

设置该属性将导致表格视图重新加载适当的索引,给我们一个很好的机会来告诉单元格是否应该展开:

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
}

结果如下:

结果

单元格现在可以正确调整自身大小。您可能会注意到动画仍然有点奇怪,但修复它不属于这个问题的范围。

结论:这比它应该的要难。我真的希望在未来看到一些改进。

于 2015-05-24T00:29:28.133 回答
-1

这是在 obj-c 中,但我相信你会处理它:

添加您的viewDidLoad:

self.tableView.estimatedRowHeight = self.tableView.rowHeight; self.tableView.rowHeight = UITableViewAutomaticDimension;

这将为您的 tableView 启用自动调整单元格大小,并且应该适用于 iOS8+

于 2015-05-07T07:30:13.903 回答