滚动到另一个时 TableView 折叠单元格:奇怪的行为

Posted

技术标签:

【中文标题】滚动到另一个时 TableView 折叠单元格:奇怪的行为【英文标题】:TableView collapsing cell while scrolling to another: weird behavior 【发布时间】:2019-01-20 16:52:52 【问题描述】:

我有一个带有聊天气泡的 tableView。


如果character countmore 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 上启用了单元格选择,您是否也在实现 UITableViewDelegatedidSelectRow 方法? @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的rowHeightestimatedRowHeight

    tableView.estimatedRowHeight = 316 // Value taken from your project. This may be something else
    tableView.rowHeight = UITableView.automaticDimension
    

    从您的代码中删除 estimatedHeightForRowAtheightForRowAt 委托方法。

【讨论】:

试过你写的,但它仍然会产生问题。我用你的解决方案更新了 Github,检查一下:单击长的,然后向下滚动(这样长的仍然在顶部可见,但较小的也可见):然后单击较小的一个。它的动画很奇怪

以上是关于滚动到另一个时 TableView 折叠单元格:奇怪的行为的主要内容,如果未能解决你的问题,请参考以下文章

滚动时 TableView 单元格很慢

将超过一半的单元格移动到另一个部分时,Tableview 崩溃

滚动时如何使tableView单元格“粘”

当我滚动时,TableView 只加载一个单元格的内容

限制tableView中显示的单元格数量,滚动到最后一个单元格时加载更多单元格

tableView 单元格内的多个 collectionView 单元格在滚动时更改设计