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()