senty
senty

Reputation: 12847

presentViewController from TableViewCell

I have a TableViewController, TableViewCell and a ViewController. I have a button in the TableViewCell and I want to present ViewController with presentViewController (but ViewController doesn't have a view on storyboard). I tried using:

@IBAction func playVideo(sender: AnyObject) {
        let vc = ViewController()
        self.presentViewController(vc, animated: true, completion: nil)
}

Error: Value of type TableViewCell has no member presentViewController


Then, I tried

self.window?.rootViewController!.presentViewController(vc, animated: true, completion: nil)

Error: Warning: Attempt to present whose view is not in the window hierarchy!


What am I doing wrong? What should I do in order to presentViewController from TableViewCell? Also how can I pass data to the new presenting VC from TableViewCell?


Update:

protocol TableViewCellDelegate
{
   buttonDidClicked(result: Int)
}

class TableViewCell: UITableViewCell {

    @IBAction func play(sender: AnyObject) {
        if let id = self.item?["id"].int {
            self.delegate?.buttonDidClicked(id)
        }
    }
}
----------------------------------------

// in TableViewController

var delegate: TableViewCellDelegate?
func buttonDidClicked(result: Int) {
    let vc = ViewController()
    self.presentViewController(vc, animated: true, completion: nil)
}

I receive error: Presenting view controllers on detached view controllers is discouraged

(Please note that I have a chain of NavBar & TabBar behind TableView.)


I also tried

 self.parentViewController!.presentViewController(vc, animated: true, completion: nil)

Same Error.


Also tried,

self.view.window?.rootViewController?.presentViewController(vc, animated: true, completion: nil)

Same Error

Upvotes: 4

Views: 13121

Answers (4)

grisVladko
grisVladko

Reputation: 161

I had the same problem and found this code somewhere on stackoverflow, but I can't remember where so it's not my code but i'll present it here.
This is what I use when I want to display a view controller from anywhere, it gives some notice that keyWindow is disabled but it works fine.

extension UIApplication
{

    class func topViewController(_ base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController?
    {
        if let nav = base as? UINavigationController
        {
            let top = topViewController(nav.visibleViewController)
            return top
        }

        if let tab = base as? UITabBarController
        {
            if let selected = tab.selectedViewController
            {
                let top = topViewController(selected)
                return top
            }
        }

        if let presented = base?.presentedViewController
        {
            let top = topViewController(presented)
            return top
        }
        return base
    }
}

And you can use it anywhere, in my case I used:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
    let vc = storyboard.instantiateViewController(withIdentifier: "WeekViewController")
    UIApplication.topViewController()?.navigationController?.show(vc, sender: nil)
}

Upvotes: 2

BabyShung
BabyShung

Reputation: 61

So self.presentViewController is a method from the ViewController. The reason you are getting this error is because the "self" you are referring is the tableViewCell. And tableViewCell doesn't have method of presentViewController.

I think there are some options you can use: 1.add a delegate and protocol in the cell, when you click on the button, the IBAction will call

self.delegate?.didClickButton()

Then in your tableVC, you just need to implement this method and call self.presentViewController

2.use a storyboard and a segue In the storyboard, drag from your button to the VC you want to go.

Upvotes: 0

Josh Grant
Josh Grant

Reputation: 176

It seems like you've already got the idea that to present a view controller, you need a view controller. So here's what you'll need to do:

  1. Create a protocol that will notify the cell's controller that the button was pressed.
  2. Create a property in your cell that holds a reference to the delegate that implements your protocol.
  3. Call the protocol method on your delegate inside of the button action.
  4. Implement the protocol method in your view controller.
  5. When configuring your cell, pass the view controller to the cell as the delegate.

Here's some code:

// 1.
protocol PlayVideoCellProtocol {
    func playVideoButtonDidSelect()
}

class TableViewCell {
// ...

// 2.
var delegate: PlayVideoCellProtocol!

// 3.
@IBAction func playVideo(sender: AnyObject) {
    self.delegate.playVideoButtonDidSelect()
}

// ...
}


class TableViewController: SuperClass, PlayVideoCellProtocol {

// ...

    // 4.
    func playVideoButtonDidSelect() {
        let viewController = ViewController() // Or however you want to create it.
        self.presentViewController(viewController, animated: true, completion: nil)
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath: NSIndexPath) -> UITableViewCell {
        //... Your cell configuration

        // 5.
        cell.delegate = self

        //...
    }
//...
}

Upvotes: 14

Breek
Breek

Reputation: 1441

You should use protocol to pass the action back to tableViewController

1) Create a protocol in your cell class

2) Make the button action call your protocol func

3) Link your cell's protocol in tableViewController by cell.delegate = self

4) Implement the cell's protocol and add your code there

let vc = ViewController()
self.presentViewController(vc, animated: true, completion: nil)

Upvotes: 7

Related Questions