如何修复滚动时从 UICollectionViewCell 中消失的图像

Posted

技术标签:

【中文标题】如何修复滚动时从 UICollectionViewCell 中消失的图像【英文标题】:How to fix images disappearing from UICollectionViewCell on scroll 【发布时间】:2019-07-21 18:35:12 【问题描述】:

在此处输入代码我正在创建一个需要在 UICollectionViewCell 中使用 UICollectionView 的应用程序。我已经成功地在单元格中添加了UICollectionView 及其所有委托方法,并且在初始加载时,一切都正确加载。然而,一旦用户尝试在主UICollectionView 中进一步向下滚动,包含内部UICollectionView 的单元格基本上就会失去他们的视野。需要注意的一个有趣细节是,只有当用户以非蜗牛速度滚动时,问题才会出现。如果滚动速度足够慢,单元格将正确加载。否则,单元格将变得畸形。

不幸的是,我处理这个问题已经有一段时间了,目前已经接近/一个多月了。我已经尝试了 ***、Medium 文章、YouTube 教程等上提出的几乎所有解决方案,但还没有找到可行的解决方案。

如果我总是将UICollectionView 添加为子视图,则此行为不会在没有条件渲染的情况下表现出来。

需要注意的一个重要细节是我的单元格是垂直调整大小的,并且不保证每个单元格都包含一个内部UICollectionView。我怀疑我的问题的一部分源于我对这种条件渲染的处理(即,如果单元格的数据源有图像,则将 UICollectionView 添加为子视图。否则,根本不要添加它)。

此外,我在我的 Xcode 项目中使用导入的资产作为图像。我没有进行任何异步调用,否则可能归因于未获取的图像和数据源有 0 个图像,而它应该具有 > 0。我在 parseDatasource(withDatasource datasource: (String, [UIImage]) 函数中的日志显示 datasource.1 包含图像数组正如预期的那样,在应该有图像的单元格中。在这一点上,我感到非常困惑,而且有点沮丧。

我的主要UIViewController控制细胞:

class ExperimentalViewController: UIViewController 

    private let cellReuseId = "cellReuseId"
    fileprivate var data = [(String, [UIImage])]()
    fileprivate let strings = [<a large array of strings>]

    private let collectionView: UICollectionView = 
        let layout = UICollectionViewFlowLayout()
        let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collection.backgroundColor = .clear
        collection.alwaysBounceVertical = true
        collection.contentInsetAdjustmentBehavior = .always

        return collection
    ()

    override func viewDidLoad() 
        super.viewDidLoad()

        view.backgroundColor = .white

        setupDummyData()
        setupCollectionView()
    

    private func setupDummyData() 
        for (i, str) in strings.enumerated() 
            var images = [UIImage]()
            if i % 2 == 0 
                images = [UIImage(named: "boxed-water-is-better-1464052-unsplash")!, UIImage(named: "boxed-water-is-better-1464052-unsplash")!, UIImage(named: "boxed-water-is-better-1464052-unsplash")!]
            
            data.append((str, images))
        
    

    private func setupCollectionView() 
        if #available(ios 13.0, *) 
            let size = NSCollectionLayoutSize(
                widthDimension: NSCollectionLayoutDimension.fractionalWidth(1),
                heightDimension: NSCollectionLayoutDimension.estimated(440)
            )

            let item = NSCollectionLayoutItem(layoutSize: size)

            let group = NSCollectionLayoutGroup.horizontal(layoutSize: size, subitems: [item])

            let section = NSCollectionLayoutSection(group: group)
            section.contentInsets = NSDirectionalEdgeInsets(top: 0, leading: 8, bottom: 0, trailing: 8)
            section.interGroupSpacing = 5

            let layout = UICollectionViewCompositionalLayout(section: section)
            collectionView.collectionViewLayout = layout
         else 
            let layout = ExperimentalFlowLayout()
            collectionView.collectionViewLayout = layout
        

        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(ExperimentalCell.self, forCellWithReuseIdentifier: cellReuseId)

        view.addSubview(collectionView)
        collectionView.anchor(top: view.topAnchor, leading: view.leadingAnchor, bottom: view.bottomAnchor, trailing: view.trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
    



extension ExperimentalViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return data.count
    

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellReuseId, for: indexPath) as! ExperimentalCell
        cell.index = indexPath
        cell.backgroundColor = .yellow
        cell.setupViews(withDatasource: data[indexPath.item])

        return cell
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat 
        return 20
    


我的UICollectionViewCell 子类

class ExperimentalCell: UICollectionViewCell 

    // https://***.com/questions/37782659/swift-ios-uicollectionview-images-mixed-up-after-fast-scroll/37784212
    // http://www.thomashanning.com/the-most-common-mistake-in-using-uitableview/
    private let imageCellReuseId = "imageCellReuseId"
    var index: IndexPath?
    var images = [UIImage]()

    private let titleLabel: UILabel = 
        let label = UILabel()
        label.numberOfLines = 0

        return label
    ()

    private let label: UILabel = 
        let label = UILabel()
        label.numberOfLines = 0

        return label
    ()

    let collectionView: UICollectionView = 
        let layout = UICollectionViewFlowLayout()
        layout.scrollDirection = .horizontal
        let collection = UICollectionView(frame: .zero, collectionViewLayout: layout)
        collection.backgroundColor = .clear

        return collection
    ()

    override init(frame: CGRect) 
        super.init(frame: frame)

        print("RYANLOG \(index?.row) INIT")
    

    required init?(coder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    override func prepareForReuse() 
        super.prepareForReuse()

        images = []
        imageView.image = nil
        collectionView.delegate = nil
        collectionView.dataSource = nil
        titleLabel.text = nil
        label.text = nil

        setupViews(withDatasource: nil)
    

    func setupViews(withDatasource datasource: (String, [UIImage])?) 
        if datasource != nil 
            parseDatasource(datasource!)
        

        contentView.addSubview(titleLabel)
        titleLabel.anchor(top: contentView.topAnchor, leading: contentView.leadingAnchor, bottom: nil, trailing: contentView.trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)

        contentView.addSubview(label)
        if images.count > 0 
            titleLabel.text = "Images"
            label.text = String(images.count)
            label.anchor(top: titleLabel.bottomAnchor, leading: contentView.leadingAnchor, bottom: nil, trailing: contentView.trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)

            setupCollectionView()
            contentView.addSubview(collectionView)
            collectionView.anchor(top: label.bottomAnchor, leading: contentView.leadingAnchor, bottom: contentView.bottomAnchor, trailing: contentView.trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 200)
         else 
            titleLabel.text = "No Images"
            label.anchor(top: titleLabel.bottomAnchor, leading: contentView.leadingAnchor, bottom: contentView.bottomAnchor, trailing: contentView.trailingAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
        

    

    private func parseDatasource(_ datasource: (String, [UIImage])) 
        label.text = datasource.0
        images = datasource.1

        print("RYANLOG \(index?.row) Images:", images.count)
    

    // MARK: - Used for self-sizing on <= iOS 12

    override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes 
        layoutIfNeeded()
        let layoutAttributes = super.preferredLayoutAttributesFitting(layoutAttributes)
        layoutAttributes.bounds.size = systemLayoutSizeFitting(UIView.layoutFittingCompressedSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .defaultLow)
        return layoutAttributes
    



extension ExperimentalCell: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout 

    fileprivate func setupCollectionView() 
        collectionView.delegate = self
        collectionView.dataSource = self
        collectionView.register(ScrollableImageView.self, forCellWithReuseIdentifier: imageCellReuseId)
    

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return images.count
    

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: imageCellReuseId, for: indexPath) as! ScrollableImageView
        cell.imageView.image = images[indexPath.item]
        return cell
    

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize 
        return CGSize(width: 200, height: 200)
    


请原谅使用print("RYANLOG ...)s。我一直在使用它们进行调试,并认为它们可能有助于显示我认为可能是问题的一部分。

ScrollableImageViewUIView 的子类。如果您认为查看此类中包含的代码会有所帮助,请告诉我!

非常感谢任何帮助!

展示有缺陷行为的 gif: https://imgur.com/a/btAXPca

【问题讨论】:

【参考方案1】:

更新:我终于使用两个不同的重用标识符解决了这个问题:"cellRuseId""imageCellReuseId"。我仍然不确定这是否是正确的解决方案,但是,现在我的 collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) 看起来像这样:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
    let reuseId = data[indexPath.item].1.count > 0 ? imageCellReuseId : cellReuseId
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseId, for: indexPath) as! ExperimentalCell
    cell.index = indexPath
    cell.backgroundColor = .yellow
    cell.setupViews(withDatasource: data[indexPath.item])
    return cell

我的收藏视图可以正确滚动并正确地重用单元格。我不会接受这个答案,因为它不是最优雅的(特别是如果您有同一个单元格的多个变体),所以如果有人有更好的答案,请给出回复!

【讨论】:

以上是关于如何修复滚动时从 UICollectionViewCell 中消失的图像的主要内容,如果未能解决你的问题,请参考以下文章

滚动 UICollectionView 时保留 UICollectionViewCell

动态修复屏幕中的 UICollectionView 单元格

swift中的UICollectionView粘性标题

点击时从 UICollectionView 中的单元格获取图像

如何在滚动时从左到右移动 div?

UICollectionView:如何检测滚动何时停止