滚动到另一个时 TableView 折叠单元格:奇怪的行为
Posted
技术标签:
【中文标题】滚动到另一个时 TableView 折叠单元格:奇怪的行为【英文标题】:TableView collapsing cell while scrolling to another: weird behavior 【发布时间】:2019-01-20 16:52:52 【问题描述】:我有一个带有聊天气泡的 tableView。
如果character count
是more than 250
,则这些气泡会缩短
如果用户点击气泡
先前的选择被取消选择(缩短) 新的选择扩展并显示全部内容 新的选择顶部约束更改(从 0 到 4)我想达到什么目标?
如果已经选择了一个长气泡,但用户选择了另一个气泡,我希望 tableView 滚动到新选择的气泡的位置。
我会分享一个关于它的视频
没有这种滚动,contentOffset 保持不变,看起来很糟糕。
(在视频中:右侧)
视频:
右:没有提到的滚动
左:滚动
https://youtu.be/_-peZycZEAE
问题来了:
在左侧,您可以注意到它有故障。
无缘无故出现随机鬼细胞。
有时它甚至会弄乱一些气泡的高度(视频中没有)
为什么会这样?
代码:
func bubbleTappedHandler(sender: UITapGestureRecognizer)
let touchPoint = sender.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint)
if indexPath == currentSelectedIndexPath
// Selected bubble is tapped, deselect it
self.selectDeselectBubbles(deselect: indexPath)
else
if (currentSelectedIndexPath != nil)
// Deselect old bubble, select new one
self.selectDeselectBubbles(select: indexPath, deselect: currentSelectedIndexPath)
else
// Select bubble
self.selectDeselectBubbles(select: indexPath)
func selectDeselectBubbles(select: IndexPath? = nil, deselect: IndexPath? = nil)
var deselectCell : WorldMessageCell?
var selectCell : WorldMessageCell?
if let deselect = deselect
deselectCell = tableView.cellForRow(at: deselect) as? WorldMessageCell
if let select = select
selectCell = tableView.cellForRow(at: select) as? WorldMessageCell
// Deselect Main
if let deselect = deselect, let deselectCell = deselectCell
tableView.deselectRow(at: deselect, animated: false)
currentSelectedIndexPath = nil
// Update text
deselectCell.messageLabel.text = self.dataSource[deselect.row].message.shortened()
// Select Main
if let select = select, let selectCell = selectCell
tableView.selectRow(at: select, animated: true, scrollPosition: .none)
currentSelectedIndexPath = select
// Update text
deselectCell.messageLabel.text = self.dataSource[select.row].message.full()
UIView.animate(withDuration: appSettings.defaultAnimationSpeed)
// Deselect Constraint changes
if let deselect = deselect, let deselectCell = deselectCell
// Constarint change
deselectCell.nickNameButtonTopConstraint.constant = 0
deselectCell.timeLabel.alpha = 0.0
deselectCell.layoutIfNeeded()
// Select Constraint changes
if let select = select, let selectCell = selectCell
// Constarint change
selectCell.nickNameButtonTopConstraint.constant = 4
selectCell.timeLabel.alpha = 1.0
selectCell.layoutIfNeeded()
self.tableView.beginUpdates()
self.tableView.endUpdates()
UIView.animate(withDuration: appSettings.defaultAnimationSpeed)
if let select = select, deselect != nil, self.tableView.cellForRow(at: deselect!) == nil && deselectCell != nil
// If deselected row is not anymore on screen
// but was before the collapsing,
// then scroll to new selected row
self.tableView.scrollToRow(at: select, at: .top, animated: false)
更新 1:添加 Github 项目
链接: https://github.com/krptia/test2
我制作了一个小版本的应用程序,以便您查看和测试我的问题所在。如果有人可以帮助解决这个问题,我将不胜感激! :c
【问题讨论】:
当你滚动时,它会重绘单元格,如果你不处理单元格来存储它所处的状态,它将绘制初始状态。 @carbonr 感谢您的回复!我该怎么做才能使它顺利?:c 这里有一点很重要。您正在使用轻击手势识别器进行选择检测。是否在 tableView 上启用了单元格选择,您是否也在实现UITableViewDelegate
的 didSelectRow
方法?
@Adeel 选择已启用,但我没有实现 didSelectRow
方法。我创建了自己的“选择系统”:因为我只希望在用户完全点击气泡时进行选择(这就是我创建委托(针对bubbleTappedHandler
)的原因,以及为什么我有currentSelectedIndexPath
变量。实际上我不需要使用tableView.selectRow(at: ...)
和tableView.deselectRow(at: ...)
,所以是的,我将删除那些行。
【参考方案1】:
首先让我们定义“不滚动”的含义 - 我们的意思是单元格保持不变。所以我们想找到一个我们想成为锚细胞的细胞。从更改前到更改后,单元格顶部到屏幕顶部的距离是相同的。
var indexPathAnchorPoint: IndexPath?
var offsetAnchorPoint: CGFloat?
func findHighestCellThatStartsInFrame() -> UITableViewCell?
return self.tableView.visibleCells.filter
$0.frame.origin.y >= self.tableView.contentOffset.y
.sorted
$0.frame.origin.y > $1.frame.origin.y
.first
func setAnchorPoint()
self.indexPathAnchorPoint = nil;
self.offsetAnchorPoint = nil;
if let cell = self.findHighestCellThatStartsInFrame()
self.offsetAnchorPoint = cell.frame.origin.y - self.tableView.contentOffset.y
self.indexPathAnchorPoint = self.tableView.indexPath(for: cell)
我们在开始做事之前调用它。
func bubbleTappedHandler(sender: UITapGestureRecognizer)
self.setAnchorPoint()
....
接下来,我们需要在进行更改后设置内容偏移量,以便单元格移回它应该在的位置。
func scrollToAnchorPoint()
if let indexPath = indexPathAnchorPoint, let offset = offsetAnchorPoint
let rect = self.tableView.rectForRow(at: indexPath)
let contentOffset = rect.origin.y - offset
self.tableView.setContentOffset(CGPoint.init(x: 0, y: contentOffset), animated: false)
接下来我们在完成更改后调用它。
self.tableView.beginUpdates()
self.tableView.endUpdates()
self.tableView.layoutSubviews()
self.scrollToAnchorPoint()
动画可能有点奇怪,因为同时发生了很多事情。我们正在同时更改内容偏移量和单元格的大小,但是如果您将手指放在顶部可见的第一个单元格旁边,您会看到它最终出现在正确的位置。
【讨论】:
这很好用!谢谢您的答复!是的,动画有点奇怪,但这是可以做到的最好的,非常感谢!一个问题:tableView在顶部取消选择一个长气泡时会跳转一秒钟,而在它下方选择了一个较小的气泡。是否可以通过重新加载 tableView 来修复跳跃?我正在试验它.. 我不知道。您可以尝试减慢整个应用程序中的动画速度,以便您可以准确地看到动画在做什么。 UIApplication.shared.keyWindow?.layer.speed = 0.5【参考方案2】:尝试在UITableViewDelegate
上使用这个willDisplay
api
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
if (indexPath == currentSelectedIndexPath)
// logic to expand your cell
// you don't need to animate it since still not visible
【讨论】:
【参考方案3】:用这个替换你的bubbleTappedHandler代码并运行并检查:
func bubbleTappedHandler(sender: UITapGestureRecognizer)
let touchPoint = sender.location(in: self.tableView)
if let indexPath = tableView.indexPathForRow(at: touchPoint)
if indexPath == currentSelectedIndexPath
currentSelectedIndexPath = nil
tableView.reloadRows(at: [indexPath], with: .automatic)
else
if (currentSelectedIndexPath != nil)
if let prevSelectedIndexPath = currentSelectedIndexPath
currentSelectedIndexPath = indexPath
tableView.reloadRows(at: [prevSelectedIndexPath, indexPath], with: .automatic)
else
currentSelectedIndexPath = indexPath
tableView.reloadRows(at: [indexPath], with: .automatic)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: [weak self] in
let currentIndexPath = self?.currentSelectedIndexPath ?? indexPath
self?.tableView.scrollToRow(at: currentIndexPath, at: .top, animated: false)
)
【讨论】:
感谢您的回复,但这并不能解决问题:c 动画不流畅,你提到的reloadRows方法更模糊【参考方案4】:您可以使用 tableView?.scrollToRow。例如
let newIndexPath = IndexPath(row: 0, section: 0) // put your selected indexpath and section here
yourTableViewName?.scrollToRow(at: newIndexPath, at: UITableViewScrollPosition.top, animated: true) // give the desired scroll position to tableView
可用的滚动位置为 .none、.top、.middle、.bottom
【讨论】:
【参考方案5】:这个故障是因为一个小错误。只要做到这2件事,问题就会得到解决。
在`viewDidLoad中设置tableView的rowHeight
和estimatedRowHeight
tableView.estimatedRowHeight = 316 // Value taken from your project. This may be something else
tableView.rowHeight = UITableView.automaticDimension
从您的代码中删除 estimatedHeightForRowAt
和 heightForRowAt
委托方法。
【讨论】:
试过你写的,但它仍然会产生问题。我用你的解决方案更新了 Github,检查一下:单击长的,然后向下滚动(这样长的仍然在顶部可见,但较小的也可见):然后单击较小的一个。它的动画很奇怪以上是关于滚动到另一个时 TableView 折叠单元格:奇怪的行为的主要内容,如果未能解决你的问题,请参考以下文章
将超过一半的单元格移动到另一个部分时,Tableview 崩溃