UIImageView animated doesn’t work properly inside UICollectionViewCell

i0S Swift Issue

Question or problem in the Swift programming language:

I would like to add a UIImageView animated inside an UICollectionViewCell so I came up with this code:

    import UIKit

class MainViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

    var items:[String] = ["one", "two", "three", "four"]

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = UIColor(red: 220/255, green: 220/255, blue: 220/255, alpha: 1.0)
        self.view.addSubview(self.collectionView)
    }

    lazy var collectionView:UICollectionView = {
        var cv = UICollectionView(frame: self.view.bounds, collectionViewLayout: self.flowLayout)
        cv.delegate = self
        cv.dataSource = self
        cv.bounces = true
        cv.alwaysBounceVertical = true
        cv.autoresizingMask = UIViewAutoresizing.FlexibleHeight | UIViewAutoresizing.FlexibleWidth
        cv.registerClass(CustomCell.self, forCellWithReuseIdentifier: "cell")
        cv.backgroundColor = UIColor(red: 220/255, green: 220/255, blue: 220/255, alpha: 1.0)
        return cv
    }()

    lazy var flowLayout:UICollectionViewFlowLayout = {
        var flow = UICollectionViewFlowLayout()
        flow.sectionInset = UIEdgeInsetsMake(2.0, 2.0, 2.0, 2.0)
        return flow
    }()


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize{

        let width:CGFloat = self.view.bounds.size.width*0.98;
        let height:CGFloat = 150.0;
        return CGSizeMake(width, height)
    }

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

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath: indexPath) as! CustomCell
        cell.layer.cornerRadius = 4
        cell.backgroundColor = UIColor.whiteColor()
        cell.imgView.animationImages = ["1","2","3","4","5", "6","7","8"].map{UIImage(named: $0)!}
        cell.imgView.animationDuration = NSTimeInterval(0.8)
        cell.imgView.startAnimating()
        return cell
    }

    func collectionView(collectionView: UICollectionView, shouldHighlightItemAtIndexPath indexPath: NSIndexPath) -> Bool {
         return true
    }

    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
        var alert = UIAlertController(title: "Alert!", message: "Cell tapped", preferredStyle: UIAlertControllerStyle.Alert)
        var action = UIAlertAction(title: "ok", style: UIAlertActionStyle.Cancel) { (dd) -> Void in }
        alert.addAction(action)
        self.presentViewController(alert, animated: true, completion: nil)
    }

}

This is my MainViewController with a CollectionView added programmatically.

    import UIKit

class CustomCell: UICollectionViewCell {

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

    lazy var imgView:UIImageView = {
        var iv = UIImageView()
        iv.contentMode = UIViewContentMode.ScaleAspectFill
        return iv
    }()

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        self.imgView.frame = CGRectMake(6,15,self.frame.width*0.2, self.frame.height*0.8)
    }
}

And this is my custom UICollectionViewCell with an UIImageView

My problem is when you tap a cell the UIImageView disappears. Trying to solve the problem I started to look another UICoolectionView delegate methods. Then I tried to use shouldHighlightItemAtIndexPath, But If I use this method returning false the animation works fine but the collection view stops responding to didSelectItemAtIndexPath.

This is a github repository with a code that shows the issue: https://github.com/gazolla/ImageAnimation (updated with solution)

SOLUTION:

With matt’s help, I made the following changes in my code:

1) add images array to highlightedAnimationImages property

let animationArray = ["1","2","3","4","5", "6","7","8"].map{UIImage(named: $0)!}
cell.imgView.highlightedAnimationImages = animationArray

2) Restart animation when cells are deselected

 func collectionView(collectionView: UICollectionView, didDeselectItemAtIndexPath indexPath: NSIndexPath) {
    if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? CustomCell{
        cell.imgView.startAnimating()
    }
 }

3) Restart animation when cells are selected

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {

    let cell = collectionView.cellForItemAtIndexPath(indexPath) as! CustomCell
    cell.imgView.startAnimating()

    //...

}

SOLUTION 2:

After some tests, I found out that when you tap and hold a cell UIImageView disappears again, So we have to restart animation in another two methods:

1) didHighlightItemAtIndexPath

func collectionView(collectionView: UICollectionView, didHighlightItemAtIndexPath indexPath: NSIndexPath) {
        if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? CustomCell{
            cell.imgView.startAnimating()
        }

  }

2) didUnhighlightItemAtIndexPath

func collectionView(collectionView: UICollectionView, didUnhighlightItemAtIndexPath indexPath: NSIndexPath) {
    if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? CustomCell{
        cell.imgView.startAnimating()
    }
}

How to solve the problem:

Solution 1:

When you select the cell, the image view looks to its highlightedAnimationImages, not its animationImages. You didn’t set the highlightedAnimationImages so you see nothing.

Here’s a screencast showing that this can work. The cell is selected when the background is gray (that is its selectedBackgroundView) but the animation continues:

enter image description here

Solution 2:

In case anyone else encounters this nasty bug and the above solutions don’t help – try calling imageView.stopAnimating() in your cell’s prepareForReuse() function.

Solution 3:

Swift 4 version of Solution 2 from @Sebastian.

func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
    if let cell = collectionView.cellForItem(at: indexPath) as? CustomCell {
        cell.imgView.startAnimating()
    }
}

func collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath) {
    if let cell = collectionView.cellForItem(at: indexPath) as? CustomCell {
        cell.imgView.startAnimating()
    }
}

Solution 4:

I had the same problem, but i did not get the “didDeselectItemAtIndexPath” to work, so my solution to the problem was just to add a button with no function over the picture.

So when you touch the picture, you will touch the button instead.

Hope this helps!