Rooncicle
Rooncicle

Reputation: 106

Adding subviews to only one UICollectionViewCell on button tap

Each UICollectionViewCell has its own button hooked up to the following action:

@IBAction func dropDown(sender:UIButton){

    var pt = sender.bounds.origin
    var ptCoords : CGPoint = sender.convertPoint(pt, toView:sender.superview);
    var ptCoords2 : CGPoint = sender.convertPoint( ptCoords, toView: collectionView!);
    var cellIndex: NSIndexPath = self.collectionView!.indexPathForItemAtPoint(ptCoords2)!
    //var i : NSInteger = cellIndex.row;
    //var i2 : NSInteger  = cellIndex.section;
    var selectedCell = collectionView?.cellForItemAtIndexPath(cellIndex) as CollectionViewCell!
    selectedCell.button.backgroundColor = UIColor.blackColor()

    for (var i = 0; i < 3; i++){
        var textView : UITextView! = UITextView(frame: CGRectMake(self.view.frame.size.width - self.view.frame.size.width/1.3, CGFloat(50 + (30*(i+1))), CGRectGetWidth(self.view.frame), CGFloat(25)))
            textView.backgroundColor = UIColor.whiteColor()
            selectedCell.contentView.addSubview(textView)
    }
}

What I want to do is add 3 subviews to only the cell that's been tapped. The subviews are added successfully, but as soon as I scroll, cells that come into view & correspond to the previously set indexPath are loaded with 3 subviews. I figure this is due to the dequeueReusableCellWithReuseIdentifier method, but I can't figure out a way around it. I considered removing the subviews on scrollViewDidScroll, but ideally I would like to keep the views present on their parent cell until the button is tapped again.

EDIT: Okay, I ditched the whole convertPoint approach and now get the cell index based on button tags:

    var selectedCellIndex : NSIndexPath = NSIndexPath(forRow: cell.button.tag, inSection: 0)
    var selectedCell = collectionView?.cellForItemAtIndexPath(selectedCellIndex) as CollectionViewCell!

Regardless, when I try to add subviews to only the cell at the selected index, the subviews are duplicated.

EDIT: I've created a dictionary with key values to track the state of each cell like so:

var cellStates = [NSIndexPath: Bool]()
for(var i = 0; i < cellImages.count; i++){
    cellStates[NSIndexPath(forRow: i, inSection: 0)] = false
}

which are set by cellStates[selectedCellIndex] = true within the dropDown function. Then in the cellForItemAtIndexPath function, I do the following check:

if(selectedIndex == indexPath && cellStates[indexPath] == true){

    for (var i = 0; i < 3; i++){
         var textView : UITextView! = UITextView(frame: CGRectMake(cell.frame.size.width - cell.frame.size.width/1.3, CGFloat(50 + (30 * (i+1))), CGRectGetWidth(cell.frame), CGFloat(25)))
         textView.backgroundColor = UIColor.whiteColor()
         cell.contentView.addSubview(textView)
         println("display subviews")
         println(indexPath)
    }
 } else {
     println("do not display subviews")
     println(indexPath)
 }

return cell

where selectedIndex, the NSIndexPath of the active cell set via the dropDown function, is compared to the indexPath of the cell being created & the cellState is checked for true.

Still no luck - the subviews are still displayed on the recycled cell. I should mention that "display subviews" and "do not display subviews" are being logged correctly while scrolling, so the conditional statement is being evaluated successfully.

MY (...hack of a...) SOLUTION! Probably breaking a bunch of best coding practices, but I assigned tags to all the created subviews, remove them at the beginning of the cellForItemAtIndexPath method, and create them again if the cellState condition returns true.

Upvotes: 1

Views: 1007

Answers (2)

amahfouz
amahfouz

Reputation: 2398

No problem. Basically, you need to store program state OUTSIDE your UI components in what is commonly called a "model". Not sure what your app is so I am going to make up an example. Assume you want to show a grid where each cell is initially green and they toggle to red when the user taps it. You would need to store the state (I.e., whether a cell has been tapped or not) in some two dimensional array, which is going to contain a Boolean for ALL cells, and not just the ones that are currently showing (assuming you have enough cells to make the grid scroll). When the user taps a cell you set the flag in corresponding array element. Then, when the iOS calls you back to provide a cell (in the dequeue method) you check the state in the array, apply the appropriate color to the UIView of the cell, then return it. That way, iOS can reuse the cell view objects for efficiency, while at the same time you apply your model state to corresponding cells dynamically. Let me know if this clear.

Upvotes: 1

amahfouz
amahfouz

Reputation: 2398

One of two things: - Disallow pooling of cells. - Maintain sufficient info in your mode to be able to draw cells depending on the model rather than on their location on screen. That is, store a bit in your model that determines whether or not to show the three views for each "logical" cell. Then, when asked to dequeue a cell, check its model and add/remove the backgrounds dynamically.

Upvotes: 0

Related Questions