20

I have an app that has a custom button in a custom cell. If you select the cell it segues to the a detail view, which is perfect. If I select a button in a cell, the code below prints the cell index into the console.

I need to access the contents of the selected cell (Using the button) and add them to an array or dictionary. I am new to this so struggling to find out how to access the contents of the cell. I tried using didselectrowatindexpath, but I don't know how to force the index to be that of the tag...

So basically, if there are 3 cells with 'Dog', 'Cat', 'Bird' as the cell.repeatLabel.text in each cell and I select the buttons in the rows 1 and 3 (Index 0 and 2), it should add 'Dog' and 'Bird' to the array/dictionary.

    // MARK: - Table View

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return postsCollection.count
}

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell: CustomCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomCell

    // Configure the cell...
    var currentRepeat = postsCollection[indexPath.row]
    cell.repeatLabel?.text = currentRepeat.product
    cell.repeatCount?.text = "Repeat: " + String(currentRepeat.currentrepeat) + " of " + String(currentRepeat.totalrepeat)

    cell.accessoryType = UITableViewCellAccessoryType.DetailDisclosureButton

    cell.checkButton.tag = indexPath.row;

    cell.checkButton.addTarget(self, action: Selector("selectItem:"), forControlEvents: UIControlEvents.TouchUpInside)


    return cell

}

func selectItem(sender:UIButton){

    println("Selected item in row \(sender.tag)")

 }
Steve Rowe
  • 237
  • 1
  • 3
  • 10

7 Answers7

67

OPTION 1. Handling it with delegation

The right way of handling events fired from your cell's subviews is to use delegation.

So you can follow the steps:

1. Above your class definition write a protocol with a single instance method inside your custom cell:

protocol CustomCellDelegate {
    func cellButtonTapped(cell: CustomCell)
} 

2. Inside your class definition declare a delegate variable and call the protocol method on the delegate:

var delegate: CustomCellDelegate?

@IBAction func buttonTapped(sender: AnyObject) {
    delegate?.cellButtonTapped(self)
}

3. Conform to the CustomCellDelegate in the class where your table view is:

 class ViewController: CustomCellDelegate

4. Set your cell's delegate

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as CustomCell
    cell.delegate = self

    return cell
}

5. Implement the required method in your view controller class.

EDIT: First define an empty array and then modify it like this:

private var selectedItems = [String]()

func cellButtonTapped(cell: CustomCell) {
    let indexPath = self.tableView.indexPathForRowAtPoint(cell.center)!
    let selectedItem = items[indexPath.row]

    if let selectedItemIndex = find(selectedItems, selectedItem) {
        selectedItems.removeAtIndex(selectedItemIndex)
    } else {
        selectedItems.append(selectedItem)
    }
}

where items is an array defined in my view controller:

private let items = ["Dog", "Cat", "Elephant", "Fox", "Ant", "Dolphin", "Donkey", "Horse", "Frog", "Cow", "Goose", "Turtle", "Sheep"] 

OPTION 2. Handling it using closures

I've decided to come back and show you another way of handling these type of situations. Using a closure in this case will result in less code and you'll achieve your goal.

1. Declare a closure variable inside your cell class:

var tapped: ((CustomCell) -> Void)?

2. Invoke the closure inside your button handler.

@IBAction func buttonTapped(sender: AnyObject) {
    tapped?(self)
}

3. In tableView(_:cellForRowAtIndexPath:) in the containing view controller class :

let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell       
cell.tapped = { [unowned self] (selectedCell) -> Void in
    let path = tableView.indexPathForRowAtPoint(selectedCell.center)!
    let selectedItem = self.items[path.row]

    println("the selected item is \(selectedItem)")
}
Vasil Garov
  • 4,473
  • 1
  • 22
  • 33
  • I like this solution better than the one above. Though, you forgot to mention that he has to actually set the delegate. Other than that the solution is solid. – Andrew Robinson Apr 28 '15 at 13:49
  • Of course I should have, my bad. Will edit it. Thanks. – Vasil Garov Apr 28 '15 at 13:54
  • Thanks so much guys. It works like a charm and has given me a little insight into delegate.. still very much a newbie. – Steve Rowe Apr 29 '15 at 19:46
  • I know I am pushing it now but I have been going around in circles.. I need to be able to add the selectedItem to an array and I have no idea how. I've tried adding it to the cellButtonTapped fund, but that doesn't work. If I select something, it must add it to the array, if I unselect it, it must remove it from the array. The idea is that once I have the array I am going to post the results of the selected items to a mySQL database when the user clicks a 'Send' button... – Steve Rowe May 01 '15 at 21:02
  • What did you try in `cellButtonTapped `? And what is the expected behavior exactly - tapping the button -> add item to the array, tapping it again -> remove item from the array? – Vasil Garov May 01 '15 at 22:46
  • I tried something like var selectedItems : [String] = String and then appending each selection to the array. This just adds the currently selected item to the array. What you described is exactly what I need it to do. To get the initial data from the mySQL database I have Post class (with JSON) that gets used to create the array postCollection. I suppose I need to create the reverse of that to get to the point where I can send what was selected back to the mySQL database.. – Steve Rowe May 02 '15 at 08:38
  • OK, I will edit my answer. Note that I am not on my Mac and I can't test the code. – Vasil Garov May 02 '15 at 08:59
  • Thanks so much. You are a star. It worked perfectly. – Steve Rowe May 02 '15 at 11:16
  • @flashadvanced thank you so much its worked perfectly.. but how i change image of button using option 2 ? – seggy Dec 09 '16 at 09:48
  • I have used a variable in custom cell class,if i alter the variable on button action using delegate method on another view, than how do i get the reflected value of that variable in cellforitem. – Akhilesh Sharma Jan 10 '17 at 09:15
10

Since you have 1 section in the table view you can get the cell object as below.

let indexPath = NSIndexPath(forRow: tag, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(indexPath) as! CustomCell!

where tag you will get from button tag.

Amit89
  • 2,920
  • 1
  • 16
  • 27
  • it will not work everytime even if its one section.suppose you deleted one cell from tableview and if you access tag as indexPath it will give you wrong index.so beware of that... – Jay Patel Jul 12 '17 at 05:20
5

Swift 3 I just get solution for access cell in @IBAction function using superview of button tag.

let cell = sender.superview?.superview as! ProductCell
var intQty = Int(cell.txtQty.text!);
intQty = intQty! + 1
let  strQty = String(describing: intQty!);
cell.txtQty.text = strQty
1
@IBAction func buttonTap(sender: UIButton) {
        let button = sender as UIButton
        let indexPath = self.tableView.indexPathForRowAtPoint(sender.center)!
}
Cao Yong
  • 590
  • 6
  • 7
1

I updated option 1 of the answer from Vasil Garov for Swift 3

1. Create a protocol for your CustomCell:

protocol CustomCellDelegate {
    func cellButtonTapped(cell: CustomCell)
} 

2. For your TableViewCell declare a delegate variable and call the protocol method on it:

var delegate: CustomCellDelegate?

@IBAction func buttonTapped(sender: AnyObject) {
    delegate?.cellButtonTapped(self)
}

3. Conform to the CustomCellDelegate in the class where your tableView is:

 class ViewController: CustomCellDelegate

4. Set your cell's delegate

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)  as CustomCell
    cell.delegate = self

    return cell
}

5. Implement the required method in your ViewController.

0

Based on Cao's answer, here is a solution to handle buttons in a collection view cell.

    @IBAction func actionButton(sender: UIButton) {
        let point = collectionView.convertPoint(sender.center, fromView: sender.superview)
        let indexPath = collectionView.indexPathForItemAtPoint(point)
}

Be aware that the convertPoint() function call will translate the button point coordinates in the collection view space. Without, indexPath will always refer to the same cell number 0

CyrIng
  • 303
  • 3
  • 6
0

XCODE 8: Important Note

Do not forget to set the tags to a different value than 0.

If you attempt to cast an object with tag = 0 it might work but in some weird cases it doesn't.

The fix is to set the tags to different values. Hope it helps someone.

MLBDG
  • 1,209
  • 16
  • 21