Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UICollectionView some cells are neither visible items nor dequeued cells

I have an issue in my UICollectionView where some cells seem to be neither part of the indexPathForVisibleItems, nor are they taken from the caching queue with dequeueReusableCell. The result is, that some cells don't receive required updates of data during a scroll and show old behavior.

For simplicity, I reduced the project to the neccessary Controllers and a minimized Storyboard. Basically, I've got a NavigationController as EntryPoint that contains the MainViewController, which itself contains a ContainerView with the CollectionViewController.

The NavigationController uses the default edit button to switch between edit and non-edit mode - this should result in an image displayed on the cells while in edit mode. Therefore I implemented setEditing and changed the images hidden property of all visible cells, and additionally i set the images hidden property while dequeuing - assuming that cells are either visible or they will be dequeued in the future.

This works fine while the CollectionView is scrolled from top to bottom. But when I switch back from Edit-Mode to Non-Edit-Mode while scrolled to the bottom and then scroll back to the top, some cells still display the image (more specific: at least the same row, which is the first non-visible row). Somehow I'd assume that the dequeued cells and the visible cells would be complementary parts of the displayed data, which should result in either the images being hidden/unhidden during the setEditing call (which works for the first 4 rows of cells) or being hidden/unhidden during the dequeuing (which works for the last few rows, except the third row in my example)

Code for the CollectionViewController:

import Foundation
import UIKit
import Photos
class CollectionViewController : UICollectionViewController {

    fileprivate let CELL_ID = "PicCell"
    fileprivate let IMAGE_VIEW_SIZE = 104
    var selectedIndex = -1
    var itemCount = 28

    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        print(self.isEditing)
        collectionView?.allowsMultipleSelection = editing
        for indexPath in (collectionView?.indexPathsForSelectedItems)! {
            collectionView?.deselectItem(at: indexPath, animated: true)
        }

        for indexPath in (collectionView?.indexPathsForVisibleItems)! {
            let cell = collectionView?.cellForItem(at: indexPath) as? PicCell
            if cell != nil {
                cell?.editing = editing
            }
        }
    }

    override func viewDidLoad() {
        self.navigationItem.rightBarButtonItem = self.editButtonItem
        collectionView?.reloadData()
    }
}

extension CollectionViewController {
    override func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return itemCount;
    }

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        print(indexPath.row)
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CELL_ID, for: indexPath) as! PicCell
        cell.editing = isEditing
        cell.isSelected = false
        return cell;
    }
}

Code for PicCell

import Foundation
import UIKit
class PicCell : UICollectionViewCell {

    @IBOutlet weak var deleteBtn: UIImageView!
    var editing:Bool = false{
        didSet {
            self.deleteBtn.isHidden = !editing
            self.deleteBtn.tintColor = UIColor.blue
        }
    }

    override var isSelected: Bool{
        didSet {
            if isSelected  && editing {
                self.deleteBtn.tintColor = UIColor.red
            } else {
                self.deleteBtn.tintColor = UIColor.blue
            }
        }
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        self.editing = false
        self.isSelected = false
    }
}

Code for MainViewController

import Foundation
import UIKit
class MainViewController: UIViewController {

    override func viewDidLoad() {
        self.navigationItem.rightBarButtonItem = self.editButtonItem
    }

    override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        collectionController?.setEditing(editing, animated: animated)
    }

    var collectionController : CollectionViewController? = nil
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ShowCollectionView" {
            collectionController = segue.destination as! CollectionViewController
        }
    }
}

as you can see I'm using constant values for the item count in a single section, which shouldn't be a big deal.

I don't know if it matters, but my cells are 104px in width and height, the image is rendered as template and the UIContainerView is 343 in width and 473 in height, i'm testing on an iPhone 7+ simulator with iOS 10.1

If there's anything missing from the storyboard, i might add some screenshots, but i'm unsure what to post exactly

Thanks in advance for your help and kind regards

Christian

Edit: just to be clear with my question: I'm looking for some advice for either obvious mistakes in my code or a way to access UICollectionViewCells that are neither in the list of visible items nor dequeued during scroll - there might be a way to access those cells i simplay don't know about

like image 585
Christian R. Avatar asked Dec 24 '22 20:12

Christian R.


1 Answers

You can change this behavior if you turn off prefetching:

collectionView?.isPrefetchingEnabled = false

Or you can keep it on, but then either:

  • hook into UICollectionViewDelegate methods didEndDisplaying and willDisplay to know as cells appear and disappear independent of cellForItemAt; or

  • have cells do some KVO on some view controller property or observe some notification that the view controller will initiate in order to know whether to change their state.

like image 189
Rob Avatar answered May 30 '23 19:05

Rob