JEL
JEL

Reputation: 1550

iOS - Load cells one by one

I want to load the cell of a collection view one by one using the API collectionView.insertItems(at:). There are many answers online which accomplish this by animating the alpha of the cell inside of willDisplayCell and other methods, for example here - link. However, nothing works perfectly. The reason for using collectionView.insertItems(at:) is that the animation is already built in and no hacks needed.

However using collectionView.insertItems(at:), I get error such as:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (5) must be equal to the number of items contained in that section before the update (5), plus or minus the number of items inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).

This is an example of what I have, it's crashing as shown above, but it's a start I think in the right direction (hopefully):

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    /// This array is populated from a network call.
    let images = [Image]()

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        animateCells()
    }

    func animateCells() {
        let indexPaths = [IndexPath(item: 0, section: 0),
                          IndexPath(item: 1, section: 0),
                          IndexPath(item: 2, section: 0),
                          IndexPath(item: 3, section: 0),
                          IndexPath(item: 4, section: 0)]

        for indexPath in indexPaths {
            let delayTime = 0.1 + Double(indexPath.row) * 0.3

            delay(delayTime) {
                self.collectionView.performBatchUpdates({
                    self.collectionView.insertItems(at: [indexPath])
                }, completion: nil)
            }
        }
    }

    func delay(_ delay: TimeInterval, closure: @escaping () -> Void) {
        let when = DispatchTime.now() + delay
        DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
    }
}

// MARK: - UICollectionViewDataSource

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

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

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)

        return cell
    }
}

One of the main problems I see is that I do return images.count inside of numberOfItemsInSection.

Any thoughts?

Upvotes: 2

Views: 485

Answers (2)

Govind Prajapati
Govind Prajapati

Reputation: 957

I made one demo with your code and found your mistake

When you are inserting items in your collection view there are already 5 items in your collection view in that position so you can not insert new cells there because your images array has only 5 items which are already been displayed.

Please find below working code ->

    class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    //Array for collectionView
    var images :[UIImage] = []

    //This array will populate from network call
    var arrAllImagesFromNetwork : [UIImage] = Array(repeating: UIImage(named: "img1")!, count: 5)

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        animateCells()

    }

    func animateCells() {
        let indexPaths = [IndexPath(item: 0, section: 0),
                          IndexPath(item: 1, section: 0),
                          IndexPath(item: 2, section: 0),
                          IndexPath(item: 3, section: 0),
                          IndexPath(item: 4, section: 0)]



        for indexPath in indexPaths {
            let delayTime = 0.1 + Double(indexPath.row) * 0.3

            delay(delayTime) {

                self.images.append(self.arrAllImagesFromNetwork[indexPath.row])

                self.collectionView.performBatchUpdates({
                    self.collectionView.insertItems(at: [indexPath])
                }, completion: nil)
            }
        }
    }

    func delay(_ delay: TimeInterval, closure: @escaping () -> Void) {
        let when = DispatchTime.now() + delay
        DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
    }
}

// MARK: - UICollectionViewDataSource

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

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

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)

        return cell
    }
}

Upvotes: 1

Luan Tran
Luan Tran

Reputation: 1142

Did you try to append element to imageswith delay too?

var images = Array(repeating: 0, count: 0)

delay(delayTime) {
     self.collectionView.performBatchUpdates({
                self.images.append(0)       
                self.collectionView.insertItems(at: [indexPath])
     }, completion: nil)
}

Upvotes: 1

Related Questions