Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why will didDeselectItemAt of UICollectionView throw an error of unexpected found nil and crash the app Swift? [duplicate]

Tags:

ios

swift

I have used a collection view to display a collection of images. It is working, but when I make use of the function didDeselectItemAt it will crash if I click certain images.

Error that occurs: fatal error: unexpectedly found nil while unwrapping an Optional value

Code setup:

numberOfItemsInSection:

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

cellForItemAt:

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

        let imageUrls = collection?.imageUrls

        // Configure the cell
        // Reset alpha of first item in collection
        if indexPath.row == 0 {
            cell.imageView.alpha = 1.0

            if videoUrl != nil {
                cell.backgroundColor = UIColor.red
                cell.imageView.alpha = 0.5
            }
        }
        cell.imageView.af_setImage(withURL: URL(string: (imageUrls?[indexPath.row])!)!)

        return cell
    }

didDeselectItemAt:

func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
        let cell = collectionView.cellForItem(at: indexPath) as! ImageCollectionViewCell

        UIView.animate(withDuration: 0.3, animations: {
            cell.imageView.alpha = 0.6
        })
    }

didSelectItemAt:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {

        print("You've tapped me. NICE!! \(indexPath.row)")

        let cell = collectionView.cellForItem(at: indexPath) as! ImageCollectionViewCell

        cell.imageView.alpha = 1

        let url = self.collection?.imageUrls?[indexPath.row]
        self.selectedImageUrl = url

        Alamofire.request(url!).responseImage(completionHandler: {
            (response) in
            if response.result.value != nil {
                self.selectedImage.image = response.result.value
            }
        })

    }

Above code is working, but will throw an error if I click certain images. The first cell of every collection view has a alpha of 100% - 1.0 and all other ones has an alpha of 0.6. What I noticed is that - except the first cell of the collection view - every 7th cell has also an alpha of 100% and if the collection view contains a videoUrl it will have a red background and an alpha of 50%..

Is this because of the reusable cells like the UITableViewController, where you use dequeueReusableCell and should you use that function too inside the didDeselectItemAt or is something else not correctly implemented?

So only the first cell in a collection view should have an alpha of 100, all other 60%. If the object has a videoUrl, the first element will have an alpha of 50% with a red background.

If there are any questions left, please let me know. Thanks in advance and have a great evening!

UPDATE:

The following print line will output below code when I click the first image of the collection view and after that the second one. This doesn't crash the app and it will still work

print("OUTPUT \(String(describing: collectionView.cellForItem(at: indexPath)))")

Above code snippet I've placed in both didSelectItemAt and didDeselectItemAt:

didSelectItemAt output:

OUTPUT Optional(<CollectionViewApp.ImageCollectionViewCell: 0x7ff61679c1a0; baseClass = UICollectionViewCell; frame = (150 0; 150 100); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x6080000347c0>>)

didDeselectItemAt output:

OUTPUT Optional(>)

When I click the first or second image and then the last image in the collection view, the error will occur and will crash my app. The touch on the first image will give me the output:

OUTPUT Optional(<CollectionViewApp.ImageCollectionViewCell: 0x7fc3cb038540; baseClass = UICollectionViewCell; frame = (150 0; 150 100); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x610000225980>>)

The last item - that will let my app crash - will throw the following output: OUTPUT Optional(<CollectionViewApp.ImageCollectionViewCell: 0x7fc3cb03afb0; baseClass = UICollectionViewCell; frame = (300 0; 150 100); clipsToBounds = YES; opaque = NO; layer = <CALayer: 0x6100002264c0>>).

So the crash can be triggered, when you click one of the first images in the collection view and after that one of the last images in the collection view, when the first images are out of canvas. I used a horizontal collection view.

like image 460
Caspert Avatar asked Dec 02 '16 20:12

Caspert


1 Answers

So the crash is happening because when selecting an item, didDeselectItemAt can be called for an item that's not visible anymore. The UICollectionView reuses UICollectionViewCells, this means that the collectionView.cellForItem(at: indexPath) method will return nil for cells that are not visible. To fix this you can use the following code:

func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
    guard let cell = collectionView.cellForItem(at: indexPath) as! ImageCollectionViewCell else {
        return //the cell is not visible
    }

    UIView.animate(withDuration: 0.3, animations: {
        cell.imageView.alpha = 0.6
    })
}

Some other suggestions I have for improving your code:

You have a lot of places where you're force unwrapping values.

Consider taking the following approach:

guard let url = self.collection?.imageUrls?[indexPath.row] else {
    fatalError("url was nil")
}

self.selectedImageUrl = url

Alamofire.request(url).responseImage(completionHandler: {
    (response) in
    if response.result.value != nil {
        self.selectedImage.image = response.result.value
    }
})

By using the guard statement you force the app to crash with an appropriate error message, which will help you when debugging. It's a better practice compared to force unwrapping.

Also, when dequeing cells, you could go for something like this:

let cellIdentifier = "ImageCell"
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as? ImageCollectionViewCell else  {
    fatalError("Wrong cell type")
}

This can happen when the cell type is different

like image 174
marosoaie Avatar answered Nov 15 '22 05:11

marosoaie