Ios UICollectionView将错误的单元格出列并显示错误的图像

Ios UICollectionView将错误的单元格出列并显示错误的图像,ios,swift,collectionview,kingfisher,Ios,Swift,Collectionview,Kingfisher,我有一个UICollectionView,在这个视图中,我可以用播放按钮显示图像或视频图像。这些单元格占据了整个屏幕的宽度,因此在同一时间屏幕上只能看到一个单元格。用户可以向左或向右滚动以显示下一个图像或视频。我可以把它和Instagram的feed进行比较 因此,当用户滚动时,总会有一个图像显示给他们。然而,当我在单元格中滚动时,比如说,有四到五个图像,它们就混在一起了。这是我的代码: func collectionView(_ collectionView: UICollectionView

我有一个
UICollectionView
,在这个视图中,我可以用播放按钮显示图像或视频图像。这些单元格占据了整个屏幕的宽度,因此在同一时间屏幕上只能看到一个单元格。用户可以向左或向右滚动以显示下一个图像或视频。我可以把它和Instagram的feed进行比较

因此,当用户滚动时,总会有一个图像显示给他们。然而,当我在单元格中滚动时,比如说,有四到五个图像,它们就混在一起了。这是我的代码:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "mediaSliderCell", for: indexPath) as! MediaSliderCell
    if(dataArray.count > 0) {
        if(dataArray[indexPath.item].mediaType == .photo) {
            let mediaURL = URL(string: "https://myURL.com/media/\(dataArray[indexPath.item].mediaURL)")
            cell.photoView.kf.setImage(with: mediaURL, options: [.targetCache(mediaCache)])
            cell.photoView.isHidden = false
            cell.videoView.isHidden = true
        } else if(dataArray[indexPath.item].mediaType == .video) {
            cell.photoView.isHidden = true
            cell.videoView.isHidden = false
            let mediaThumbURL = URL(string: "https://myURL.com/media/\(dataArray[indexPath.item].mediaThumbURL!)")
            let mediaURL = URL(string: "https://myURL.com/media/\(dataArray[indexPath.item].mediaURL)")!
            cell.videoView.placeholderView.kf.setImage(with: mediaThumbURL, options: [.targetCache(mediaCache)])
            cell.videoView.mediaURL = mediaURL
            cell.videoView.testLabel.text = "indexPath.item: \(indexPath.item)"
        }
    }
    return cell
}
我知道排队是如何工作的。我过去也有过这样的问题。到目前为止,我还尝试了我所知道的一切来避免这个问题:

  • 在prepareForReuse()中将imageView.image设置为nil
  • 使用if-else语句并在cellForItemAt中将imageView.image设置为nil,但这会返回一个黑屏(因此没有图像)
它发生在我使用视频的时候。对于照片,我还没有看到,但也可能是这样。我怎样才能解决这个问题

这是我的
MediaSliderCell

class MediaSliderCell: UICollectionViewCell {
    override func prepareForReuse() {
        videoView.placeholderView.kf.cancelDownloadTask()
        videoView.placeholderView.image = UIImage()
        photoView.image = UIImage()
    }

    var photoView: UIImageView = {
        let photoView = UIImageView()
        photoView.translatesAutoresizingMaskIntoConstraints = false
        photoView.backgroundColor = .black
        photoView.isHidden = true
        return photoView
    }()

    var videoView: VideoView = {
        let videoView = VideoView()
        videoView.translatesAutoresizingMaskIntoConstraints = false
        videoView.backgroundColor = .black
        return videoView
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViews()
    }

    func setupViews() {
        addSubview(photoView)
        addSubview(videoView)
        photoView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        photoView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        photoView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        photoView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
        videoView.topAnchor.constraint(equalTo: topAnchor).isActive = true
        videoView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
        videoView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
        videoView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
VideoView
也是自定义的,但这只是一个
AVPlayer
的实现,不应该影响单元格的行为。这是相当多的代码,所以我宁愿不张贴它,因为它不处理正在显示的图像。你知道这里发生了什么吗?当我在
cellForItemAt
中打印索引路径和URL时,我总是得到我应该看到的图像的正确URL。但是图像混淆了。我使用翠鸟下载图像并缓存它们以备将来使用

编辑:基本上,就我目前所了解的情况而言,在
cellForItemAt
中,返回了正确的数据(至少在我打印出来时是这样)。我注意到每当iOS想要检索单元格时,都会检索单元格,所以iOS可能会再次获取单元格0、单元格1、单元格2和单元格1的数据。。。但这不应该是问题所在

首先,我认为问题可能出在翠鸟的下载方法上,因为下载可能会在稍后完成,当单元格已经更改时。但是,同样,单元格也使用了错误的mediaURL,因此不仅图像是错误的,而且我链接到它的mediaURL也是错误的(例如mediaURL和mediaThumbURL)。我认为问题出在其他地方,也许在视频视图中?这可能被重复使用或错误地联系在一起吗

提到我的dataArray是这样构建的可能也会有所帮助:

struct MediaData: Codable {
    var mediaType: MediaType // enum .photo or .video
    var mediaURL: String
    var mediaThumbURL: String?
}
接下来,我只需附加所有项,因此需要以正确的顺序显示它们,这就是为什么我依赖indexPath.item从数组中正确地选择它们


EDIT2:我现在注意到返回的indexath有时也会出错。我在单元格中添加了一个
UILabel
,并添加了
cell.testLabel.text=indexPath.item
。有时,对于第一个单元格(应该是indexPath项0),结果是3。因此,在向左和向右滚动之后,第一个单元格具有indexPath(0,3)。这是为什么?我如何修复它?

在对KingsFisher库进行了一些研究后,我发现,如果图像的缓存是磁盘缓存,那么cancelDownloadTask()不会阻止KingsFisher从磁盘缓存中检索图像。同样,对于磁盘缓存,它会将获取的数据切换到另一个DispatchQueue,kf.setImage会在一小段时间后从磁盘缓存中设置映像。 这会导致有趣的“行为”(我会说这不是记录在案的行为或bug)。
如何复制它:
1.运行你的应用程序。
2.滚动至收藏视图的末尾。
3.等待一段时间(直到所有映像下载并缓存到磁盘)。
4.重新启动你的应用程序。
5.等待映像从磁盘缓存加载并显示在第一个UICollectionViewCell中。
6.向右滚动到第二个单元格并返回第一个单元格,无需等待。
7.您将看到第二个单元格中的图像,而不是第一个单元格中的图像。
原因是如果KingsFisher同步从内存缓存中获取图像。但对于磁盘缓存,它使其与另一个DispatchQueue异步

// This url is loaded from disk cache
let url1 = URL(string: "some url 1")
// This url is loaded from memory cache
let url2 = URL(string: "some url 2")

// Because url1 is taken from disk cache
// it would be setted asynchronously in future.
imageView.kf.setImage(with: url1)

// Becase url2 is taken from memory cache
// it setted synchronously in present.
imageView.kf.setImage(with: url2)

// As result imageView constains image from url1 instead of image from url2.

/*
 One of the solutions is to use .fromMemoryCacheOrRefresh option
 that would igrnore disk cache, and load images synchronously from memory cache.

 imageView.kf.setImage(with: url1, options: [.fromMemoryCacheOrRefresh])
 imageView.kf.setImage(with: url2, options: [.fromMemoryCacheOrRefresh])

 imageView will show image from url2.
 */
此外,在SetupView中,必须将标签和视频视图放置在单元格的contentView上,而不是单元格上

contentView.addSubview(photoView)
contentView.addSubview(videoView)

这也能解释为什么mediaURL是错误的吗?它是否会阻止队列,直到其任务完成,然后才设置mediaURL?您建议的解决方案fromMemoryCache是否会阻止再次下载,因此只需等待图像在内存中可用?1)不,不是。UICollectionView可能还有另一个问题,因为您的帖子中的所有代码看起来都不错。2) 它将同步地将url设置为图像,并执行下一次导入(设置mediaURL)。3) 我们需要在PrepareForuse中或在cellForRowAt中设置图像之前调用cancelDownloadTask以阻止下载。我发现另一个问题。您可以在prepareforuse中调用super.prepareforuse()