1

I have a table view cell with a button. When that button is clicked, I want to present an alert controller. What I have implemented now is a delegate pattern, like the following:

https://stackoverflow.com/a/49199783/6724161

The problem is that I use this table view cell across my app in multiple view controllers, so I don't want to copy and paste the same alert controller code to each view controller.

How can I present an alert controller on any view controller when the button in the table view cell is clicked?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
user6724161
  • 195
  • 2
  • 15
  • you must check out the notification pattern to achieve this use. – eemrah Oct 16 '19 at 21:16
  • @elia I'm not sure how a notification pattern would provide me the ability to present to a view controller – user6724161 Oct 16 '19 at 21:52
  • Its not for presenting any controller, its for listening any notification form Living cell or controller and way to generalize it as you describe if I don’t get it wrong – eemrah Oct 16 '19 at 21:54
  • I am writing an answer as I mentioned about notification. So there can be a good way to mix somethings. – eemrah Oct 16 '19 at 22:02

3 Answers3

4

You could use notifications, as pointed out in the comments, but I would do something else.

1. In your custom cell class, I would add a property called parentVC of type UIViewController:

class YourTableViewCell: UITableViewCell {

    var parentVC: UIViewController!

    ...

}

2. Then, when you are dequeueing the cell, I would set the cell's parentVC to self, like so:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "YourCellIdentifier") as! YourTableViewCell
    //`self` is the view controller that the cell's table view is held in
    cell.parentVC = self
    return cell
}

3. Finally, whenever your custom cell's button is clicked, simply present the alert controller from parentVC:

class YourTableViewCell: UITableViewCell {

    var parentVC: UIViewController!

    @IBAction func customButtonPressed(_ sender: Any) {
        let alertController = UIAlertController(title: "Your alert title.", message: "Your alert message.", preferredStyle: .alert)
        parentVC.present(alertController, animated: true, completion: nil)
    }

    ...

}

Another variation (as suggested by rmaddy)

You could simply change the button within your UITableViewCell to recursively check the next responder until it is of type UIViewController.

First add the following extension to UIView. This will find the UIView's parent view controller:

extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.next
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

You could simply change the button within your UITableViewCell to recursively check the next responder until it is of type UIViewController. Once you find this UIViewController, you can have it present the UIAlertController, or any other view controller:

Source: Given a view, how do I get its viewController?

Once you find this UIViewController, you can have it present the UIAlertController, or any other view controller:

class YourTableViewCell: UITableViewCell {

    ...

    @IBAction func customButtonPressed(_ sender: Any) {
        let alertController = UIAlertController(title: "Your alert title.", message: "Your alert message.", preferredStyle: .alert)

        guard let parentVC = self.parentViewController else { return }   
        parentVC.present(alertController, animated: true, completion: nil)
    }

    ...

}
David Chopin
  • 2,780
  • 2
  • 19
  • 40
  • Or simply have the cell find its own view controller by walking the responder chain. Then you don't even need to have each view controller set itself on each cell. – rmaddy Oct 16 '19 at 21:33
  • So recursively check a `UIView`'s next responder until it is a `UIViewController`? Like in mxcl's answer here: https://stackoverflow.com/questions/1372977/given-a-view-how-do-i-get-its-viewcontroller ? – David Chopin Oct 16 '19 at 21:40
0

Your can present using AppDelegates window property.

let alert = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
let action = UIAlertAction(title: "OK", style: .cancel, handler: nil)
alert.addAction(action)
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)

Better you have a validation based on your application rootViewController type to avoid issues. For example,

  • if the rootViewController is a navigationController, you can take the top viewController of the navigationController and present the alert.

  • if the rootViewController is UIViewController, your can present directly from the rootViewController.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
RAJA
  • 1,214
  • 10
  • 13
0

You can solve your problem with using Notification pattern.

Firstly I highly recommend to build your notification diagram with extend a Notification.Name for your alert stuff.

extension Notification.Name {
    static let notificationForAlertingStuff = Notification.Name("isAlertReallyAlert")
}

Then create an observer notification for alert stuff.

override func viewDidLoad() {
   NotificationCenter.default.addObserver(self, selector: #selector(onDidReceiveData(_:)), name: Notification.Name("isAlertReallyAlert"), object: nil)
}

Create your own notification handler function.

@objc private func onDidReceiveData(_ notification: NSNotification){
        print("notification has fired. user info => \(notification.userInfo) ")

      // present alert
    }

I think you must extend UIViewController and add a simple function for alerting with getting title and description parameters. For now there is just printing info from the Notification.

Then when you fire any Notification use this function in button click or row click.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        var alertData = ["title": "Alert Title", "description": "Alert Description"]

        NotificationCenter.default.post(name: Notification.Name("isAlertReallyAlert"), object: self, userInfo: alertData)
    }

So maybe you can specified and add feature for user info to prepare any alert.

Lastly here is the extension for the UIViewController to use UIAlertController efficiently.

extension UIViewController{


    func shotAlert(title:String, description:String){
        // Create Alert Controller

    }

}

// GOOD POINT:

Let's assume your view controllers A, B and C are living in UINavigationController stack like A -> B -> C, so if you add observer in A controller you can fire any notification from the C as you describe Notification.Name correctly.

eemrah
  • 1,603
  • 3
  • 19
  • 37
  • Why do you create a static `Notification.Name` but then never use it? – rmaddy Oct 17 '19 at 00:35
  • Also note that the question is about a table cell needing to display an alert without the need of replicating alert related code in every view controller that happens to use that cell class. This answer doesn't address that at all. – rmaddy Oct 17 '19 at 00:36
  • @rmaddy It can be used as Notification.Name.staticVariable or like I used describing way. Another your comment, so I just extend UIViewCpntroller to exact alert function, just can use it every UITableViewCells inside with Notification I think and that’s way of I address it. Opposition to David’s answer I am not thinking / want to to send any UIViewController reference for making easy for it, in my solution it can be used like above I think – eemrah Oct 17 '19 at 04:24