使用文本视图更新自调整表格查看单元格的高度

时间:2016-02-07 21:24:42

标签: ios uitableview autolayout uitextview

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

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

如何在不布置整个表格视图的情况下反映单元格文本视图中的更改?

3 个答案:

答案 0 :(得分:3)

限制解决方案

reloadRowsAtIndexPaths的问题在于UITextFieldresignFirstResponder,因为从本质上来说,重新加载了单元格:即。{em}。毁

相反,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秒,则不会进行更新。这大大减少了闪烁。

Frame Delay

↻ replay animation

►在GitHub上找到此解决方案,并在Swift Recipes上找到其他详细信息。

答案 1 :(得分: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()
}

为像我这样晚会的其他人发帖:)

答案 2 :(得分:1)

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

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

触发单个单元格刷新

reloadRowsAtIndexPaths并不像reloadData()那么激烈。这是一个准时的索引刷新,不需要被tableView.beginUpdatestableView.endUpdates包围,您可以提供您选择的动画方法,例如.Fade

请参阅documentation中的reloadRowsAtIndexPaths(_:withRowAnimation:)

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

Animated demo