在 UICollectionView 中点击单元格返回错误的索引

Posted

技术标签:

【中文标题】在 UICollectionView 中点击单元格返回错误的索引【英文标题】:Tapping cell in UICollectionView returns wrong index 【发布时间】:2020-09-08 14:13:49 【问题描述】:

我真的不知道我错过了什么。我已经设置了UICollectionView,并且内容设置正确。 cellForItemAt 部分如下所示:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailsEventImageCell", for: indexPath) as? DetailsEventImageCell
    
    print("Loaded - IndexPathItem: \(indexPath.item)")
    
    cell?.backgroundColor = .clear
    cell?.contentView.layer.cornerRadius = 10
    cell?.contentView.layer.masksToBounds = true
    cell?.eventImage.image = images[indexPath.row]
    
    return cell ?? UICollectionViewCell()

collectionView 正确显示了单元格,但问题出在这里。当我进入编辑模式并随机点击所有单元格时,有时它会返回错误的索引并标记错误的单元格(主要是在它旁边),如下图所示:

The cell I tap should return indexPath.item = 5 but it does return 4 and marks cell 4 for deletion instead of 5.

由于单元格设置正确,我不知道为什么它偶尔会为所选单元格返回错误的indexPath。如果我点击一个单元格两次,它会突然返回正确的indexPath。如果我取消选择所有单元格并重试,有些单元格会再次返回错误的indexPath

这是我的didSelectItemAt 代码

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) 
    print(indexPath.item)
    if isEditing 
        if let cell = collectionView.cellForItem(at: indexPath) as? DetailsEventImageCell 
            cell.isInEditingMode = isEditing
            cell.isSelected = true
        
    

以下两个函数也可能是罪魁祸首,但我只是不明白为什么它会正确设置collectionView,然后点击返回错误值。

这是我的自定义单元格:

class DetailsEventImageCell: UICollectionViewCell 

@IBOutlet weak var eventImage: UIImageView!
@IBOutlet weak var selectedForDeletionImage: UIImageView!

var isInEditingMode: Bool = false 
    didSet 
        if !isInEditingMode 
            //selectedForDeletionImage.isHidden = true
            isSelected = false
        
    


override var isSelected: Bool 
    didSet 
        if isInEditingMode 
            selectedForDeletionImage.isHidden = isSelected ? false : true
        
    

这是我的setEditing 代码

override func setEditing(_ editing: Bool, animated: Bool) 
        super.setEditing(editing, animated: animated)
        
        collectionView.allowsMultipleSelection = editing
        
        //Deselect all selected cells
        if !editing 
            if let indexPaths = collectionView.indexPathsForSelectedItems 
                for indexPath in indexPaths 
                    self.collectionView.deselectItem(at: indexPath, animated: false)
                
            
        
        
        
        let indexPaths = collectionView.indexPathsForVisibleItems
        for indexPath in indexPaths 
            if let cell = collectionView.cellForItem(at: indexPath) as? DetailsEventImageCell 
                cell.isInEditingMode = editing
            
        
    

我希望有人可以帮助我。

【问题讨论】:

【参考方案1】:

我认为如果你有很多单元格,上述实现将无法工作,因为单元格是可重复使用的。

    您可以在数组中收集选定的索引路径,在每个 didSelece/didDeselect 方法上重新加载。

    在 cellforRowAtItem 方法中更新 UI。

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)    
        if isEditing 
           self.selectedIndexPaths.append(indexPath)
           self.collectionView.reloadData()
        
    
    

在 didDeselect 方法中会像...

func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) 
    if let index = self.selectedIndexPaths.firstIndex(of: indexPath) 
        self.selectedIndexPaths.remove(at: index)
    
    self.collectionView.reloadData()

最后 cellforRowAtItem 会像...

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DetailsEventImageCell", for: indexPath) as? DetailsEventImageCell

print("Loaded - IndexPathItem: \(indexPath.item)")

cell?.backgroundColor = .clear
cell?.contentView.layer.cornerRadius = 10
cell?.contentView.layer.masksToBounds = true
cell?.eventImage.image = images[indexPath.row]
if self.selectedIndexpaths.contains(indexPath) 
   cell.isSelected = true
 else 
   cell.isSelected = false

return cell ?? UICollectionViewCell()

【讨论】:

感谢您的回答。严肃的问题:将选定的 indexPaths 存储在 var 中和使用 collectionView.indexPathsForSelectedItems 有什么区别?这个变量似乎总是存储正确的 indexPaths,即使在使用 100 多个单元格进行测试并在整个地方进行选择时也是如此。 可能是操作系统或设备或性能等问题。 对我来说,正如我在添加的答案中所写的那样,它适用于实际的硬件设备。只有在模拟器中才会出现问题。我的 Mac 已经很旧了,所以它实际上可能是一个性能问题。有人说“不”,但对我投了反对票。真的很感谢你的帮助!祝你有美好的一天【参考方案2】:

经过数小时的尝试,我发现这似乎是与 ios 模拟器相关的问题。我无法访问所有设备尺寸,所以我有时会使用它进行快速测试。

在模拟器中“缓慢”点击单元格确实可以按预期工作,但如果我在单元格内点击得太快,它似乎无法跟上并再次选择前一个单元格。

在实际设备上,可以以任何速度点击单元格,并且可以按预期工作。

编辑:这个问题似乎只出现在我的 Mac Mini 2012 年末(macOS Catalina 10.15.6)上 XCode 11.7 上的 iOS 模拟器上 我的 Macbook Pro 15" 2019 和我尝试过的所有硬件设备(iPhone 11 Pro、ipad Air、iPhone SE 2nd Gen)在相同的项目和相同的 OS/XCode 构建中都能按预期工作。

【讨论】:

虽然单元格已正确初始化和重用。当重复使用单元格并更新数据时,我进行了调试打印。如前所述,数据也正确显示。当我在编辑模式之外点击单元格时,它们总是返回正确的 indexPath。当我进入编辑模式并快速连续选择多个单元格时,它偶尔会返回错误的 indexPath。 当我只显示 5 个单元格并且没有单元格从视图中消失和重新出现以供重复使用时,也会发生这种情况。该问题仍然只存在于模拟器中,在设备上它始终按预期工作并始终返回正确的值。 好吧,我对此并不教条。但我对直接修改单元格而不是修改源数据并将其流入界面的概念持教条主义的观点。如果您可以制作一个非常小的测试项目来演示该问题,也许我可以下载它(例如从 github)并尝试一下。你必须明白,我已经用选定的状态制作集合视图 并且从未见过这个问题;你一定想知道为什么会这样。 我知道你的意思,也许我的措辞是错误的,因为我不是以英语为母语的人。我所做的基本上是修改 collectionView 之外的图像数组,并在加载/重用时将正确的数据(图像)分配给特定的单元格。我在单元格中添加了一个 imageView,当单元格的状态为“isSelected”时出现,而当 isSelected 的状态为 false 时不出现。我没有从委托方法中修改我的源数据(图像)。很高兴你能提供帮助,但你的回答可能会有点自大,因为很明显我是新来的。 我不怀疑你比我有更多的经验,我实际上是为了好玩而为 iOS 编程,但多年来我从未在任何 collectionView 或 tableView 上遇到过这个问题(在我初步尝试)。我在两部 iPad 和 3 部不同的 iPhone 上尝试了该代码,它总是准确地返回了我对控制台的期望。唯一一次失败是我尝试使用 iOS 模拟器时。我使用的 Mac Mini 现在已经 7 岁了,它不再是最快的了,所以它实际上可能是造成这种行为的原因。

以上是关于在 UICollectionView 中点击单元格返回错误的索引的主要内容,如果未能解决你的问题,请参考以下文章

在滚动期间点击时,UICollectionView 不再识别单元格上的点击?

点击时将图像设置为 UICollectionView 单元格

如何知道在 UICollectionView 中点击了自定义单元格的哪一部分?

在第二次点击时取消选择 UICollectionView 单元格

在点击时突出显示 uicollectionview 单元格

为啥我的 UICollectionView 单元格在我的 swift ios 应用程序中不可点击?