MrDank
MrDank

Reputation: 2050

How to update the button tag which is part of UICollectionViewCell after a cell is deleted in UICollectionView?

Here's a problem which I have been stuck at for quite some time now. Here's the code

let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)
collectionViewLove?.performBatchUpdates({() -> Void in

self.collectionViewLove?.deleteItemsAtIndexPaths([indexPath])
self.wishlist?.results.removeAtIndex(indexPath.row)

self.collectionViewLove?.reloadData()}, completion: nil)}

I have a button inside each UICollectionViewCell which deletes it on clicking. The only way for me to retrieve the indexPath is through the button tag. I have initialized the button tag in

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell    

However every time I delete, the first time it deletes the corresponding cell whereas the next time it deletes the cell follwing the one I clicked. The reason is that my button tag is not getting updated when I call the function reloadData(). Ideally, when I call the reloadData() ,

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell 

should get called and update the button tag for each cell. But that is not happening. Solution anyone?

EDIT:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    collectionView.registerNib(UINib(nibName: "LoveListCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! LoveListCollectionViewCell
    cell.imgView.hnk_setImageFromURL(NSURL(string: (wishlist?.results[indexPath.row].image)!)!, placeholder: UIImage(named: "preloader"))
    let item = self.wishlist?.results[indexPath.row]
    cell.layer.borderColor = UIColor.grayColor().CGColor
    cell.layer.borderWidth = 1
    cell.itemName.text = item?.title
    cell.itemName.numberOfLines = 1
    if(item?.price != nil){
        cell.price.text = "\u{20B9} " + (item?.price.stringByReplacingOccurrencesOfString("Rs.", withString: ""))!
    }
    cell.price.adjustsFontSizeToFitWidth = true
    cell.deleteButton.tag = indexPath.row
    cell.deleteButton.addTarget(self, action: "removeFromLoveList:", forControlEvents: .TouchUpInside)
    cell.buyButton.tag = indexPath.row
    cell.buyButton.backgroundColor = UIColor.blackColor()
    cell.buyButton.addTarget(self, action: "buyAction:", forControlEvents: .TouchUpInside)
    return cell
}

Upvotes: 1

Views: 1576

Answers (3)

Chetan9007
Chetan9007

Reputation: 908

I was facing the similar issue and I found the answer by just reloading collection view in the completion block.

Just update your code like.

let indexPath = NSIndexPath(forRow: sender.tag, inSection: 0)

collectionViewLove?.performBatchUpdates({
    self.collectionViewLove?.deleteItemsAtIndexPaths([indexPath])
    self.wishlist?.results.removeAtIndex(indexPath.row)
}, completion: {
    self.collectionViewLove?.reloadData()
})

which is mentioned in UICollectionView Performing Updates using performBatchUpdates by Nik

Upvotes: 1

Dallas Johnson
Dallas Johnson

Reputation: 1536

It's probably not a good idea to be storing data in the cell unless it is needed to display the cell. Instead your could rely on the UICollectionView to give you the correct indexPath then use that for the deleting from your data source and updating the collectionview. To do this use a delegate pattern with cells.

1.Define a protocol that your controller/datasource should conform to.

protocol DeleteButtonProtocol {
    func deleteButtonTappedFromCell(cell: UICollectionViewCell) -> Void
}

2.Add a delegate property to your custom cell which would call back to the controller on the delete action. The important thing is to pass the cell in to that call as self.

class CustomCell: UICollectionViewCell {
     var deleteButtonDelegate: DeleteButtonProtocol!
    // Other cell configuration

    func buttonTapped(sender: UIButton){
        self.deleteButtonDelegate.deleteButtonTappedFromCell(self)
    }
}

3.Then back in the controller implement the protocol function to handle the delete action. Here you could get the indexPath for the item from the collectionView which could be used to delete the data and remove the cell from the collectionView.

class CollectionViewController: UICollectionViewController, DeleteButtonProtocol {
    // Other CollectionView Stuff

    func deleteButtonTappedFromCell(cell: UICollectionViewCell) {
        let deleteIndexPath = self.collectionView!.indexPathForCell(cell)!
        self.wishList.removeAtIndex(deleteIndexPath.row)

    self.collectionView?.performBatchUpdates({ () -> Void in
        self.collectionView?.deleteItemsAtIndexPaths([deleteIndexPath])
     }, completion: nil)
   }
    }

4.Make sure you set the delegate for the cell when configuring it so the delegate calls back to somewhere.

    override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        //Other cell configuring here
        var cell = collectionView.dequeueReusableCellWithReuseIdentifier("identifier", forIndexPath: indexPath)
        (cell as! CustomCell).deleteButtonDelegate = self
        return cell
    }
}

Upvotes: 3

Palpatim
Palpatim

Reputation: 9272

A couple of things:

  1. You're doing too much work in cellForItemAtIndexPath--you really want that to be as speedy as possible. For example, you only need to register the nib once for the collectionView--viewDidLoad() is a good place for that. Also, you should set initial state of the cell in the cell's prepareForReuse() method, and then only use cellForItemAtIndexPath to update with the custom state from the item.

  2. You shouldn't reload the data until the deletion is complete. Move reloadData into your completion block so the delete method is complete and the view has had time to update its indexes.

  3. However, it would be better if you didn't have to call reloadData in the first place. Your implementation ties the button's tag to an indexPath, but these mutate at different times. What about tying the button's tag to, say, the wishlist item ID. Then you can look up the appropriate indexPath based on the ID.

Revised code would look something like this (untested and not syntax-checked):

// In LoveListCollectionViewCell
override func prepareForReuse() {
    // You could also set these in the cell's initializer if they're not going to change
    cell.layer.borderColor = UIColor.grayColor().CGColor
    cell.layer.borderWidth = 1
    cell.itemName.numberOfLines = 1
    cell.price.adjustsFontSizeToFitWidth = true
    cell.buyButton.backgroundColor = UIColor.blackColor()
}

// In your UICollectionView class

// Cache placeholder image since it doesn't change
private let placeholderImage = UIImage(named: "preloader")

override func viewDidLoad() {
    super.viewDidLoad()
    collectionView.registerNib(UINib(nibName: "LoveListCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "Cell")
}

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier("Cell", forIndexPath: indexPath) as! LoveListCollectionViewCell
    cell.imgView.hnk_setImageFromURL(NSURL(string: (wishlist?.results[indexPath.row].image)!)!, placeholder: placeholderImage)

    let item = self.wishlist?.results[indexPath.row]
    cell.itemName.text = item?.title
    if(item?.price != nil){
        cell.price.text = "\u{20B9} " + (item?.price.stringByReplacingOccurrencesOfString("Rs.", withString: ""))!
    }
    cell.deleteButton.tag = item?.id
    cell.deleteButton.addTarget(self, action: "removeFromLoveList:", forControlEvents: .TouchUpInside)
    cell.buyButton.tag = item?.id
    cell.buyButton.addTarget(self, action: "buyAction:", forControlEvents: .TouchUpInside)
    return cell
}

func removeFromLoveList(sender: AnyObject?) {
    let id = sender.tag
    let index = wishlist?.results.indexOf { $0.id == id }
    let indexPath = NSIndexPath(forRow: index, inSection: 0)

    collectionViewLove?.deleteItemsAtIndexPaths([indexPath])
    wishlist?.results.removeAtIndex(index)
}

Upvotes: 3

Related Questions