Что такое проблема слайд-шоу (фликер) при скроллинге в UITableView/UICollectionView и как её решить?

Ответ

Проблема слайд-шоу (фликера) — это визуальный артефакт, когда изображения в переиспользуемых ячейках подгружаются асинхронно и появляются в неправильном порядке при быстром скроллинге.

Причина:

  1. Переиспользование ячеек: Ячейка с indexPath A начинает загрузку изображения, но переиспользуется для indexPath B до завершения запроса.
  2. Отсутствие отмены: Старый асинхронный запрос завершается и устанавливает изображение в уже переиспользованную ячейку, «перезаписывая» контент для нового indexPath.

Решение — проверка актуальности: Перед установкой изображения нужно убедиться, что ячейка всё ещё отображает тот же контент.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    // 1. Устанавливаем уникальный идентификатор для текущей конфигурации ячейки
    let currentTag = indexPath.row
    cell.tag = currentTag

    // 2. Запускаем асинхронную загрузку
    loadImage(for: indexPath) { [weak cell] image in
        DispatchQueue.main.async {
            // 3. Устанавливаем изображение, только если тег ячейки совпадает
            if cell?.tag == currentTag {
                cell?.imageView?.image = image
            }
        }
    }
    return cell
}

Дополнительные меры:

  • В prepareForReuse() ячейки следует отменять старые запросы на загрузку.
  • Использовать специализированные библиотеки (например, SDWebImage или Kingfisher), которые инкапсулируют эту логику и кэширование.

Ответ 18+ 🔞

А, слушай, вот эта хуйня со слайд-шоу, когда картинки в таблице пляшут как угорелые при быстром скролле! Пиздец, знакомо, да? Ты листаешь, а там вместо котика уже грузится хомяк, а потом бац — и котик снова вылезает, но уже в ячейке с рецептом борща. Ебать-копать, глаза сломаешь.

А корень зла, блядь, простой, как три копейки. Таблица-то хитрая жопа — она ячейки переиспользует, экономит память, молодец вроде. Но получается пиздопроебибна: ты для ячейки №1 начал грузить картинку, палец дернул, ячейку №1 уже отправили нахуй и переиспользовали для ячейки №15. А старый запрос-то, мудя, ещё не отменился! Он приползает через секунду и впендюривает картинку от №1 в ячейку №15, которая уже про хомяков думает. Вот тебе и фликер, ёпта.

Лечится это, внатуре, проверкой актуальности. Надо перед установкой картинки спросить ячейку: «А ты ещё та самая, для которой я старался?». Делается через тег, например. Смотри, как гениально просто:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    // 1. Втыкаем ячейке бирку с её текущим номером
    let currentTag = indexPath.row
    cell.tag = currentTag

    // 2. Гоним асинхронно грузить картинку
    loadImage(for: indexPath) { [weak cell] image in
        DispatchQueue.main.async {
            // 3. А вот тут хитрая жопа! Даём картинку только если бирка совпадает.
            if cell?.tag == currentTag {
                cell?.imageView?.image = image
            }
        }
    }
    return cell
}

Вот и вся магия, блядь. Если ячейку уже переиспользовали, у неё тег новый, и старый запрос, припёршись со своим устаревшим изображением, получит отлуп. Чих-пых тебя в сраку, фликер!

Ну и, конечно, в prepareForReuse() старые запросы надо отменять, чтоб они не висели в памяти как шерстяные пидарасы. А вообще, возьми Kingfisher или SDWebImage — они эту всю хуйню уже сто лет как обернули в конфетку, там и кэш, и отмена, и анимации. Зачем, блядь, велосипед изобретать, когда можно просто не ебать себе мозг?