【问题标题】:Updating the Height of a Self-Sizing Table View Cell with a Text View使用文本视图更新自调整大小的表格视图单元格的高度
【发布时间】:2016-02-07 21:24:41
【问题描述】:

我有一个类似于 Mail 中的撰写屏幕的表格视图,其中一个单元格用于文本输入。我希望通过使用自动布局将单元格的 contentView 约束到其 UITextView 子视图来自动调整单元格的大小。表视图的rowHeightestimatedRowHeight 设置为UITableViewAutomaticDimension。此外,文本视图的scrollEnabled 属性设置为false,以便它报告其intrinsicContentSize

然而,当用户输入文本行时,单元格不会更新其高度,即使文本视图本身更新其内在内容大小。据我所知,手动请求此请求的唯一方法是让表格视图调用beginUpdates()endUpdates()。然后单元格的高度会正确更新,但这也会导致整个表格视图的重新布局。这是(别人的)sample code demonstrating the problem

如何在不布局整个表格视图的情况下反映单元格文本视图的变化?

【问题讨论】:

  • 你是怎么解决这个问题的?有没有不抖动的解决方案?
  • 到目前为止,我只找到了部分解决方案。我尝试的第一种方法,只是在textViewDidChange 中调用beginUpdates()endUpdates(),接近我想要的。但是你必须处理整个表格视图布局变得无效,以及随后的滚动。或者,重新加载行可以工作如果它可以在没有明显中断的情况下完成,但这开始导致我宁愿避免使用 Core Animation 的 hacky KVO 东西。
  • beginUpdates+endUpdates 的问题:当单元格顶部看不见时开始抖动。 reloadRowsAtIndexPaths 的问题:取消编辑,因此键盘消失。我在 节流 解决方案方面取得了相当大的成功。

标签: ios uitableview autolayout uitextview


【解决方案1】:

节流解决方案

reloadRowsAtIndexPaths 的问题在于,UITextFieldresignFirstResponder,因为本质上是重新加载单元格:即销毁

相反,beginUpdates()endUpdates() 确实会保留现有的单元格,但在包含 scrollEnabled UITextView 的单元格上调用时会抖动,如果每个 textViewDidChange 都触发。

限制更新频率

此解决方案基于流行的textViewDidChange 方法,仅通过推迟更新来完全减少或停止闪烁。

子类UITableViewCell

class TableViewTextViewCell : UITableViewCell, UITextViewDelegate {
    var refreshCell:(() -> Void)? = nil
    var textViewDirtyCount = 0

    // MARK: - UITextViewDelegate
    func textViewDidChange(_ textView: UITextView) {
        textViewDirtyCount += 1
        perform(#selector(TableViewTextViewCell.queuedTextVewDidChange),
                with: nil,
                afterDelay: 0.3) // Wait until typing stopped
    }

    func textViewDidBeginEditing(_ textView: UITextView) {
        textViewDirtyCount = 0 // initialize queuedTextVewDidChange
    }

    func textViewDidEndEditing(_ textView: UITextView) {
        textViewDirtyCount = -1 // prevent any further queuedTextVewDidChange
    }

    func queuedTextVewDidChange() {
        if textViewDirtyCount > 0 {
            textViewDirtyCount -= 1
            if 0 == textViewDirtyCount, let refreshCell = refreshCell {
                refreshCell()
            }
        }
    }
}

出列和更新闭包:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier(
        "cell", forIndexPath: indexPath) as! TableViewTextViewCell

    cell.refreshCell = {
        () -> Void in
        tableView.beginUpdates()
        tableView.endUpdates()
    }
    return cell
}

请注意在输入最后一个字符后延迟 0.3 秒;如果自上次更改后不到 0.3 秒,则不会进行更新。这显着减少了闪烁。

↻ replay animation


► 在GitHub 上查找此解决方案,在Swift Recipes 上查找更多详细信息。

【讨论】:

  • 我非常感谢您的努力,但是当界面的主要焦点涉及编辑文本时,这仍然不是一个合适的解决方案。在用户停止输入之前,每一个新行都会被隐藏,如果涉及文本附件,这会变得更加明显。不过,希望其他人会发现这个答案很有用。
  • 直到第二个单词我都经历了模糊的行。不用担心工作量,它在发货的产品中。显然,我对稳健、通用的解决方案非常感兴趣。
【解决方案2】:

如果在调用之前禁用 UIView 动画,则 beginUpdates 方法有效。

//inside of cellForRowAtIndexPath
cell.performCellUpdates = { [unowned self] in
    UIView.setAnimationsEnabled(false)   //the secret sauce, prevents the jitter
    self.tv.beginUpdates()
    self.tv.endUpdates()
    UIView.setAnimationsEnabled(true)    //don't forget to turn animations back on
}

...

//inside of cell subclass
func textViewDidChange(_ textView: UITextView) {
    textView.sizeToFit()
    performCellUpdates()
}

为像我这样迟到聚会的其他人发布此信息:)

【讨论】:

  • 谢谢,我其实很早就试过了……不幸的是,这种方法在用户编辑文本时阻止了表格视图滚动(即使文本视图的光标在可见内容区域之外结束) ,所以在我的情况下它不可用。不过这种方法不会导致“抖动”,因此对其他人来说可能就足够了。
【解决方案3】:

您无法更改现有UITableViewCell 的高度。您想告诉UITableView 单元格无效,然后重新加载。

在重新加载单元格时 (cellForRowAtIndexPath),您可以传递新的、更新的单元格。然后你用tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)触发单个单元格刷新

reloadRowsAtIndexPaths 没有reloadData() 那样激烈。它是一个准时的索引刷新,不需要被tableView.beginUpdatestableView.endUpdates包围,你可以提供一个你选择的动画方法,比如.Fade

documentation 中查看reloadRowsAtIndexPaths(_:withRowAnimation:)


另请访问this Stack Overflow answer,其中详细描述了更改单元格高度。

【讨论】:

  • 好吧,确实您不必重新加载单元格……beginUpdates 的文档指出,“您也可以使用此方法,然后使用 endUpdates 方法来为行中的更改设置动画高度而不重新加载单元格。”然而,一个 layout 传递仍然是为整个 table view 执行的,这会将其滚动到顶部,从而中断文本输入。
  • 我已经澄清了我的答案:调用tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) 是触发单个单元格刷新所需的全部内容。
  • 我上面描述的方法已经更新了布局而不重新加载任何单元格。由于我的单元格中的文本视图会随着用户编辑文本而更新,因此重新加载单元格似乎不是一个选项(至少不是我想出来的)。我的问题是是否有办法指定只需要布置具有更新内容的特定单元格。从你的帖子开始,我想没有。
  • stackoverflow.com/a/26599389/218152 上描述了这个难题:(1) 如果您将beginUpdates/endUpdatesscrollEnabled=false 一起使用,您最终会在发短信时不受控制地抖动变大,因为表格是从上到下重新绘制的,然后滚动回插入点。 (2) 如果你使用reloadRowsAtIndexPaths,你会失去编辑。一种方法是将scrollEnabledUITextView 绑定到becomeFirstResponder/resignFirstResponder。视觉效果:单元格仅在编辑完成后调整大小。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-10-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多