首页 > 解决方案 > UICollectionView 没有通过 cellForItemAt 正确更新所有单元格

问题描述

我有以下设置:一个UICollectionView自定义单元格,用户可以在其中选择多个单元格,然后执行一些繁重的图像操作。现在我尝试使用该操作的结果更新用户选择的选定单元格,这将由子类自定义单元格的函数产生。对于所有可见的单元格,该函数在用户按下按钮时调用,对于所有其他单元格,cellForItemAt为了最有效,这是通过发生的。

但是,我现在面临的问题是,可见单元格都已更新,但是在滚动单元格之后或之前可见单元格不会通过更新cellForItemAt而只有在前后滚动时才会更新。请参阅随附的视频。

出于演示目的,我只UIView为图像操作显示绿色。由于该操作占用大量资源,因此我不能使用任何UICollectionViewreloadData或类似操作,因为它们会取消选择选定的单元格,并且手动重新选择会导致闪烁。

视图控制器

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    // data source
    var items: [String] = ["Lynna", "Vickie", "Emmerich", "Chere", "Patrizius", "Micky", "Sarena", "Jewell", "Audrye", "Rockwell", "Lari", "Travis", "Ber", "Oneida", "Bethanne", "Geri", "Gray", "Wolfie", "Hallsy", "Vanna", "Aline", "Winslow", "Amelita", "Janek", "Samuel", "Jaclin", "Dick", "Jeanine", "Brandy", "Munroe", "Felice", "Melonie", "Esta", "Bria", "Kenon", "Roarke", "Gerry", "Carmel", "Conrado", "Jaquelin", "Avrit", "Noam", "Kleon", "Edith", "Carine", "Jackie", "Franni", "Riccardo", "Theda", "Virgina", "Carleen", "Gregorio", "Taylor", "Ellsworth", "Adolphus", "Annabella", "Natalya", "Celina", "Cathrine", "Evie", "Gasper", "Aguste", "Jory", "Farrell", "Natasha", "Deborah", "Pietro", "Lucila", "Yuri", "Micheil", "Claus", "Nelson", "Elisa", "Alfons", "Nicolea", "Sofia", "Geordie", "Anette", "Myrtice", "Tami", "Lucien", "Brigida", "Claire", "Brennan", "Kendal", "Jillana", "Adelaida", "Charles", "Mart", "Hastie", "Dewain", "Heida", "Karisa", "Arleyne", "Margi", "Brent", "Natalie", "Ashton", "Teodorico", "Prentice", "Rolando", "Tootsie", "Fonz", "Tremayne", "Bernardo", "Guthrie", "Hendrick", "Constantina", "Lin", "Reece", "Horace", "Celka", "Amata", "Dunc", "Franchot", "Niko", "Janna", "Jacklyn", "Eddy", "Ashely", "Angelica", "Pinchas", "Krishnah", "Edmon", "Darnell", "Alyson", "Pearle", "Ashla", "Chickie", "Ada", "Elane", "Bethanne-Two", "Lon", "Marlin", "Karissa", "Ellswerth", "Lisbeth", "Cortie", "Lucille", "Lissa", "Markos", "Alys", "Orrin", "Nancy", "Pepe", "Lauren", "Wyndham", "Belle", "Gordie", "Marcille", "Idaline", "Hillary", "Enrique", "Murielle", "Juliann", "Beatrisa", "Lavinia", "Noni", "Justino", "Hasty", "Sile", "Kiley", "Shepard", "Nickie", "Ly", "Dannel", "Tressa", "Merci", "Rog", "Frans", "Bernita", "Ginnie", "Niko-Two", "Sheilakathryn", "Whitman", "Kendell", "Florenza", "Marybeth", "Drusi", "Paulina", "Fina", "Moria", "Jacobo", "Rowan", "Mariejeanne", "Luelle", "Vicki", "Auria", "Trisha", "Ken", "Carline", "Dorry", "Forrester", "Wylie", "Elset", "Francyne", "Bondy", "Demott", "Erik", "Elenore", "Correy", "Isadora", "Mason", "Barris", "Marlee", "Haslett", "Lorette", "Arlyn", "Genna", "Trude", "Parnell", "Albina", "Spencer", "Lefty", "Luigi", "Winnie", "Torie", "Zita", "Bert", "Danyelle", "Aldis", "Rivy", "Niels", "Monte", "Sandy", "Georgeta", "Durante", "Cobbie", "Kathryn", "Dinnie", "Fanchon", "Say", "Ethelred", "Vick", "Betteanne", "Orazio", "Mannie", "Jinny", "Kerrin", "Abagail", "Reyna", "Tomlin", "Shelton", "Noble", "Massimiliano", "Madel", "Cayla", "Mathias", "Tod", "Liana", "Celia", "Antoni", "Ruthann", "Irvine", "Leisha", "Geri-Two", "Marlie", "Pascale", "Latia", "Minne", "Arlette", "Rhys", "Flint", "Sollie", "Hinda", "Igor", "Marcella", "Wilma", "Marijo", "Marika", "Pooh", "Roland", "Sergent", "Fawnia", "Valaree", "Evonne", "Angeline", "Welbie", "Mace", "Niles", "Grannie", "Berenice", "L;urette", "Nert", "Rheba", "Cristie", "Lazar", "Amil", "Yanaton", "Luella", "Herold", "Enrique-Two", "Joanna", "Dom", "Crista", "Ellyn", "Ema", "Romona", "Dona", "Madelina", "Nevsa", "Conway", "Laverna", "Orion", "Leighton", "Earle"]

    // stores the selected items
    var selectedItems = [String]()

    var applyImageOperation = false

    override func viewDidLoad() {
        super.viewDidLoad()

        // Set up collection view
        let layout = UICollectionViewFlowLayout()
        layout.minimumLineSpacing = 0
        layout.minimumInteritemSpacing = 0
        collectionView.collectionViewLayout = layout

        collectionView.allowsMultipleSelection = true
        collectionView.delegate = self
        collectionView.dataSource = self
        self.collectionView!.register(MyCustomCell.self, forCellWithReuseIdentifier: "CELL")
        self.collectionView!.contentInset = UIEdgeInsets(top: 6, left: 6, bottom: 6, right: 6)
    }

    @IBAction func btnSelectAllPressed(_ sender: Any) {
        // Add all items to the seleted array
        selectedItems = [String]() // empty the current selection first
        for item in items {
            selectedItems.append(item)
        }

        // Update the collection view visually by selecting all cells
        for i in 0..<collectionView.numberOfItems(inSection: 0) {
            collectionView.selectItem(at: IndexPath(row: i, section: 0), animated: false, scrollPosition: [])
        }
    }

    @IBAction func btnApplyImageOperationPressed(_ sender: Any) {
        // set image operation mode true if we have items selected
        if selectedItems.count > 0 {
            applyImageOperation = true
        }

        // All visible cells should call activateImageOperationMode() - all others will do in cellForItem
        for visibleCell in collectionView.visibleCells {
            if let cell = visibleCell as? MyCustomCell {
                if let name = cell.titleLabel.text {
                    if selectedItems.contains(name) {
                        cell.activateImageOperationMode()
                    }
                }
            }
        }
    }
}




extension ViewController: UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: ((collectionView.frame.width-12)/3), height: ((collectionView.frame.width-12)/3))
    }

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

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CELL", for: indexPath) as! MyCustomCell
        cell.titleLabel.text = items[indexPath.row]

        // if image operation is not active, we don't show / remove the view
        if !applyImageOperation {
            cell.deactivateImageOperationMode()
        }
        // if image operation is active, we show the view for all selected items
        else {
            // Check if cell is selected
            if selectedItems.contains(items[indexPath.row]) {
                cell.activateImageOperationMode()
            }
            // If cell is not selected
            else {
                cell.deactivateImageOperationMode()
            }
        }

        return cell
    }

    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        // Add item to selectedItems
        if !selectedItems.contains(items[indexPath.row]) {
            selectedItems.append(items[indexPath.row])
        }
    }

    func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
        // Remove item from selectedItems
        if selectedItems.contains(items[indexPath.row]) {
            selectedItems.removeAll{ $0 == items[indexPath.row] }
        }
    }
}

我的自定义单元格

import UIKit

class MyCustomCell: UICollectionViewCell {

    let titleLabel: UILabel = {
        let l = UILabel()
        l.translatesAutoresizingMaskIntoConstraints = false
        l.text = "n/a"
        l.textColor = .red
        l.textAlignment = .center
        return l
    }()

    let bgColor: UIView = {
        let v = UIView()
        v.translatesAutoresizingMaskIntoConstraints = false
        v.backgroundColor = .lightGray
        return v
    }()

    var myImageOperationView: UIView?



    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        self.commonInit()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.commonInit()
    }

    func commonInit(){
        // Configure Selection Background View
        selectedBackgroundView = {
            let view = UIView()
            view.backgroundColor = .red
            return view
        }()

        // Set std BG color
        contentView.addSubview(bgColor)
        bgColor.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 6).isActive = true
        bgColor.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -6).isActive = true
        bgColor.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: 6).isActive = true
        bgColor.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: -6).isActive = true

        // Setup label
        bgColor.addSubview(titleLabel)
        titleLabel.topAnchor.constraint(equalTo: bgColor.topAnchor, constant: 0).isActive = true
        titleLabel.bottomAnchor.constraint(equalTo: bgColor.bottomAnchor, constant: 0).isActive = true
        titleLabel.leftAnchor.constraint(equalTo: bgColor.leftAnchor, constant: 0).isActive = true
        titleLabel.rightAnchor.constraint(equalTo: bgColor.rightAnchor, constant: 0).isActive = true
    }

    func activateImageOperationMode() {
        if myImageOperationView == nil {
            // HERE WOULD BE SOME HEAVY IMAGE OPERATION
            // Adding this view is just a placeholder for demonstration
            myImageOperationView = UIView()
            myImageOperationView!.translatesAutoresizingMaskIntoConstraints = false
            myImageOperationView!.backgroundColor = .green
            bgColor.addSubview(myImageOperationView!)
            myImageOperationView!.topAnchor.constraint(equalTo: bgColor.topAnchor, constant: 0).isActive = true
            myImageOperationView!.bottomAnchor.constraint(equalTo: bgColor.bottomAnchor, constant: 0).isActive = true
            myImageOperationView!.leftAnchor.constraint(equalTo: bgColor.leftAnchor, constant: 0).isActive = true
            myImageOperationView!.rightAnchor.constraint(equalTo: bgColor.rightAnchor, constant: 0).isActive = true

            // bring label to top
            bgColor.bringSubviewToFront(titleLabel)
        }
    }

    func deactivateImageOperationMode() {
        if myImageOperationView != nil {
            myImageOperationView!.removeFromSuperview()
            myImageOperationView = nil
        }
    }
}

显示行为的视频

在此处输入图像描述

标签: iosswiftuicollectionview

解决方案


UICollectionView 默认为prefetchingEnabled== YES,这意味着集合视图将在需要显示单元格之前请求单元格。如果应用程序的状态发生变化,使得已经获取的单元格需要以不同的方式显示,您可以实现collectionView:willDisplayCell:forItemAtIndexPath:更新单元格的方法。

看起来这正是您的问题……您打开了一个应该改变单元格外观的功能,但是已获取但不可见的单元格不会得到更新。实施collectionView:willDisplayCell:forItemAtIndexPath:应该可以解决问题。


推荐阅读