使用自动布局约束动态调整表视图单元格的大小

Józ*_*sza 14 uitableview ios autolayout ios-autolayout

更新

在我的最新发现之后,我完全修改了这个问题.

目标

我的目标是实现以下效果:

  1. 有一个简单的表格视图
  2. 用户选择一行
  3. 选定的行会展开,显示原始行下方的另一个标签

请注意,我知道,这可以通过插入/删除所选单元格下方的单元格来实现,我已经使用该方法成功实现了.

这次,我想尝试使用Auto Layout约束来实现这一点.

当前状态

我有一个示例项目可供任何人查看,也打开了一个问题.总结一下,这是我到目前为止所尝试的内容:

我在这里作为演员有以下观点:

  • 单元格的内容视图
  • 顶视图,包含主标签("主视图")
  • 主视图下方的底视图,包含最初隐藏的标签("详细视图")

我已经按照以下方式在我的单元格中设置了自动布局约束(请注意,这是严格的伪语言):

  • 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

我有以下结果:

当前状态

所以效果是有效的,但不完全是我最初的想象.我没有推动主视图,而是希望细胞的高度在显示细节视图时增长,并在隐藏时收缩.

我在运行时检查了我的布局层次结构:

  • 初始状态还可以.内容视图的高度等于我的主视图的高度(在这种情况下,它是125点).

截图初始

  • 选择单元格后,细节视图的高度约束将增加到44个点,并且两个视图将垂直堆叠.但不是单元格的内容视图扩展,而是主视图缩小.

截图详细

我需要的是:表格视图单元格内容视图的高度应该等于

  • 当详细视图的高度约束为0时,主视图的高度(当前这是有效的)
  • 正确设置详细视图的高度约束时的主视图高度+详细视图高度(这不起作用).

我如何设置约束来实现这一目标?

Józ*_*sza 13

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

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

在Main和Detail Views中,我最初将标签设置为水平和垂直居中.这对于自我调整细胞是不够的.我需要的第一件事是使用垂直间距约束而不是简单对齐来设置我的布局:

主视图

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

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

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

详细视图

主要想法如下:

  • 默认情况下,单元格已折叠.为此,我们必须以编程方式将Detail Container的高度约束的常量设置为0.由于其优先级高于单元格内容视图中的垂直约束,后者将被忽略,因此将隐藏详细信息容器.
  • 当我们选择单元格时,我们希望它扩展.这意味着,垂直约束必须采取控制:我们将优先级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
}
Run Code Online (Sandbox Code Playgroud)

要触发行为,我们必须覆盖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
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我已经介绍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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

设置属性将导致表视图重新加载适当的索引,为我们提供了一个告诉单元格的完美机会,是否应该展开:

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
}
Run Code Online (Sandbox Code Playgroud)

最后一步是在以下方面启用自我调整viewDidLoad():

override func viewDidLoad() {
    super.viewDidLoad()
    tableView.contentInset.top = statusbarHeight
    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 125
}
Run Code Online (Sandbox Code Playgroud)

结果如下:

结果

细胞现在可以自己调整大小 你可能会注意到动画仍然有点奇怪,但修复它不属于这个问题的范围.

结论:这比应该的更难.我真的希望看到未来会有一些改进.