ken
ken

Reputation: 2662

How to go to another View Controller when an element inside table view is being tapped?

I have a UIView inside a TableViewCell,when users tapped in this UIView,it go to another view controller and pass a data along.

So inside TableViewCellClass I done the following :

I set up a protocol and detected the "Tap" gesture,when user table the UIView.This part is working fine:

protocol MyDelegate : class {
        func runThisFunction(myString : String)
}

class MyTableViewCell: UITableViewCell {

        weak var delegate : MyDelegate?

        ...other code here

        //here detect the tap 
        let tap = UITapGestureRecognizer(target: self, action: #selector(hereTapped))
        self.myUIElementInThisCell.addGestureRecognizer(tap)

}

@objc func hereTapped(sender: UITapGestureRecognizer? = nil){
    self.delegate?.runThisFunction(myString: "I am a man")
}

So in my view controller which contain this TableView,I done the following :

I extend out the MyDelegate as subclass,and then attach the protocol function inside it as below

class MyViewController: UIViewController,MyDelagate{

func runThisFunction(myString : String) {
     print("Tapped in view controller")
    self.performSegue(withIdentifier: "MySegue",sender : self)
}

 override func viewDidLoad() {
    super.viewDidLoad()

    let tableViewCell = MyTableViewCell()
    tableViewCell.delegate = self
 }
}

Result:

After done all the stuff above,when I tapped the UIView,it didnt perform the segue as stated in MyViewControllerClass,even the print() command also didnt execute.

So what I missing out? Please give me a solution.Thanks

Upvotes: 0

Views: 186

Answers (4)

Shehata Gamal
Shehata Gamal

Reputation: 100503

The problem is that these 2 lines:

let tableViewCell = MyTableViewCell()
tableViewCell.delegate = self

are not related to the shown cells in the table , it's a cell created on the fly so

set delegate in cellForRow for the cell that you will actually waiting a delegate trigger from them

 cell.delegate = self

like this

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

         let cell = areaSettTable.dequeueReusableCell(withIdentifier:cellID) as! MyTableViewCell
         cell.delegate = self
   }

Upvotes: 1

Milan Nosáľ
Milan Nosáľ

Reputation: 19737

Problem is in your viewDidLoad:

// you create a new cell
let tableViewCell = MyTableViewCell()
// set its delegate
tableViewCell.delegate = self
// and then the cell is not used for anything else

Basically you are not setting the delegate for the cells that are being presented, but for another instance that you create in viewDidLoad.

You have to set a delegate in cellForRowAt to make sure the proper cells get the delegate set:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "yourIdentifier", for: indexPath) as! MyTableViewCell
    cell.delegate = self
    return cell
}

This will set the delegate for those cells, that are presented.


Alternatively, I would recommend using didSelectRowAt from UITableViewDelegate (if your MyViewController implements it):

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    if indexPath.row == 0 { // only if it is the first cell (I assume that MyTableViewCell is first)
        runThisFunction(myString: "I am a man")
    }
}

Upvotes: 0

MichaelV
MichaelV

Reputation: 1261

Delegation in not very good here, indeed if you want to change object that didn't passed to the cell explictitly.

My advice is to use closure:

typealias CellTapHandler = (String)->()

class CustomCell: UITableViewCell {
    var handler: CellTapHandler?

    @objc func hereTapped(sender: UITapGestureRecognizer? = nil) {
        handler?("String or whatever you want to get back from cell.")
    }
    //...
}

and set up it from view controller

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "yourIdentifier", for: indexPath) 
    (cell as? MyTableViewCell).handler = { [weak self] string in  } 
    return cell
}

PS: As I said, usually you want to pass object related to cell further. It's difficult to do with delegation, as you had to pass to cell some additional token, to determine object by the cell, o pass object itself breaking Model-View separation paradigm. But it can be done easily with closures:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let object = self.myData[indexPath.row]
    let cell = tableView.dequeueReusableCell(withIdentifier: "yourIdentifier", for: indexPath) 
    (cell as? MyTableViewCell).handler = { [weak self] string in  
          self.performSegue(withIdentifier: "MySegue",sender : object)
    } 
    return cell
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
     if (let myObj = sender as? MyObjType, let subsequentVC = segue.destination as? NextViewController) {
           subsequentVC.selectedMyObject = myObj
     }
}

Upvotes: 0

nikano
nikano

Reputation: 1248

The problem is that the delegate for MyTableViewCell instances is not defined.

When you do:

override func viewDidLoad() {
    super.viewDidLoad()

    let tableViewCell = MyTableViewCell()
    tableViewCell.delegate = self
}

You are setting a delegate for an object that will be destroyed just when the method viewDidLoad() finishes.

Solution 1

In order to avoid this, you have to set the delegate inside the cellForRow method.

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

    // Other configurations

    return cell
}

Solution 2

You can also use the UITableViewDelegate methods in order to capture the user interaction with the cells.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let cell = tableView.cellForRow(at: indexPath)

    self.performSegue(withIdentifier: "MySegue",sender : self)
}

This way you avoid all the MyDelegate protocol thing. This would be my preferred option.

Upvotes: 1

Related Questions