UICollectionView удаляет неправильную ячейку и показывает неправильное изображение - PullRequest
1 голос
/ 02 ноября 2019

У меня есть UICollectionView, в котором я показываю изображение или изображение видео с помощью кнопки воспроизведения. Ячейки занимают всю ширину экрана, поэтому на экране одновременно видна только одна ячейка. Пользователи могут прокручивать влево или вправо, чтобы показать следующее изображение или видео. Лучше всего сравнить его с новостями из Instagram.

Итак, пользователю всегда отображается изображение при прокрутке. Однако, когда я прокручиваю ячейки, когда есть, скажем, четыре или пять изображений, они смешиваются. Это мой код:

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
}

Я знаю, как работает снятие очереди. У меня были проблемы с этим в прошлом. Я также попробовал все, что я знаю до сих пор, чтобы избежать этой проблемы:

  • установите для imageView.image значение nil в prepareForReuse ()
  • , используйте оператор if else и установите imageView. image to nil в cellForItemAt, но это возвращает черный экран (поэтому тогда изображение отсутствует)

Это происходит, когда я использую видео. Что касается фотографии, я еще не видел этого, но это может быть и так. Как я могу это исправить?

Это мой 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 и не долженвлиять на поведение клеток. Это довольно много кода, поэтому я не буду публиковать его, так как он не имеет отношения к отображаемым изображениям. Есть идеи, что здесь происходит? Когда я печатаю indexPaths и URL-адреса в cellForItemAt, я всегда получаю правильный URL-адрес изображения, которое я должен увидеть . Но изображения перепутаны. Я использую KingFisher для загрузки изображений и кэширования их для будущего использования.

РЕДАКТИРОВАТЬ: В основном, насколько я понимаю ситуацию, в cellForItemAt возвращаются правильные данные (по крайней мере, когда я их печатаю)вне). Я замечаю, что ячейки извлекаются всякий раз, когда iOS хочет получить их, поэтому iOS может извлекать данные для ячейки 0, затем ячейки 1, затем ячейки 2, затем снова ячейки 1 ... но это не должно быть проблемой.

Сначала я подумал, что проблема может быть в методе загрузки KingFisher, поскольку загрузка может закончиться позже, когда ячейка уже будет изменена. Но опять же, ячейка также использует неправильный mediaURL, поэтому не только изображение является неправильным, но также и mediaURL, на который я ссылаюсь (mediaURL, например, https://myURL.com/media/test.mp4, и mediaThumbURL, например, https://myURL.com/media/thumb/test.jpeg).Я думаю, что проблема кроется где-то еще, может быть, в VideoView? Может ли это быть повторно использовано или как-то неправильно связано?

Может быть также полезно упомянуть, что мой dataArray построен так:

struct MediaData: Codable {
    var mediaType: MediaType // enum .photo or .video
    var mediaURL: String
    var mediaThumbURL: String?
}

Затем я просто добавляю все свои элементы, и поэтому мне нужно показать их в правильном порядке, поэтому я полагаюсь на indexPath.item, чтобы правильно выбрать их из массива.

EDIT2: теперь я замечаю, что возвращаемый indexPath иногда тоже неверен. Я добавил UILabel в свою ячейку и добавил cell.testLabel.text = indexPath.item. Иногда для первой ячейки (которая должна быть элементом indexPath 0) я получаю 3 какрезультат. Итак, самая первая ячейка, после прокрутки влево и вправо, затем имеет indexPath (0,3). Почему это так и как я могу это исправить?

1 Ответ

0 голосов
/ 02 ноября 2019

После некоторого исследования библиотеки KingsFisher я обнаружил, что если кеш вашего образа - кеш диска, то метод cancelDownloadTask () не помешает KingsFisher извлекать образ из кеша диска. Также для дискового кеша он переключает получение данных в другое DispatchQueue, и kf.setImage через некоторое время устанавливает образ из дискового кеша. Это приводит к интересному «поведению» (я бы сказал, что это не задокументированное поведение или, возможно, ошибка).
Как воспроизвести его:
1. Запустите приложение.
2. Прокрутите до конца вашей коллекцииview.
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.
 */

Также вы в setupViews должны размещать метку и просмотр видео не в ячейке, а в ее contentView.

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