1

I am trying to call a UIAlertController from within my UITtableViewCell when my function is called. It gives me an error saying present is not available. I understand it's not within a ViewController. I am looking for an approach to access it.

override func awakeFromNib() {
    super.awakeFromNib()
    // Initialization code

    let tapGestureShareImageView = UITapGestureRecognizer(target: self, action: #selector(self.shareImageTouchUpInside))
    shareImageView.addGestureRecognizer(tapGestureShareImageView)
    shareImageView.isUserInteractionEnabled = true
}

@objc func shareImageTouchUpInside() {
    showAction()
}

func showAction() {
    let alertController = UIAlertController(title: "Action Sheet", message: "What do you like to do", preferredStyle: .alert)

    let okButton = UIAlertAction(title: "Done", style: .default, handler: { (action) -> Void in
        print("Ok button tapped")
    })

    let deleteButton = UIAlertAction(title: "Skip", style: .destructive, handler: { (action) -> Void in
        print("Delete button tapped")
    })

    alertController.addAction(okButton)
    alertController.addAction(deleteButton)

    present(alertController, animated: true, completion: nil)
}
rmaddy
  • 314,917
  • 42
  • 532
  • 579
Amir
  • 13
  • 5
  • Ideally, don't show a view controller from a cell. Instead, create a delegate or a callback function and pass the event to the view controller and present alert from there. – Sulthan Mar 09 '18 at 18:58

3 Answers3

4

You can try to use delegate

protocol AlertShower{
    func showAlert(TableCustomCell)
}

class TableCustomCell: UITableViewCell {
    var delegate: AlertShower?

    @IBAction func showClicked(_ sender: UIButton) {
        self.delegate?.alertShower(sender:self)
    }
}

in the VC

class viewController: UIViewController, AlertShower {

    override func viewDidLoad() {
        super.viewDidLoad()

    }


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

         let cell = areaSettTable.dequeueReusableCell(withIdentifier:CellIdentifier1) as! TableCustomCell

         cell.delegate = self

         return cell
    }

    func showAlert(sender:TableCustomCell) {

      // show alert here

    }
}
Shehata Gamal
  • 98,760
  • 8
  • 65
  • 87
  • 2
    1. Protocol names (like class names) should start with uppercase letters. 2. It would be useful to pass the cell as an argument to the delegate method. Then the delegate knows which show triggered the request and could possibly use that information to get data from its data model. – rmaddy Mar 09 '18 at 18:29
  • Yes I am going to pass the postID in it ..I was just going blank on accessing the UIAlertController – Amir Mar 09 '18 at 18:41
  • 1
    By the way, using callbacks, e.g. `var onShare: (() -> Void)?` can be a better idea since it allows for better code organization. – Sulthan Mar 09 '18 at 19:35
  • @Sulthan How would your solution for `var onShare: (() -> Void)?` look like? I want to show an alert when loading the cell's content fails but I'm not sure what you mean. – Neph Apr 23 '21 at 11:42
1

Present is only available to ViewControllers. You are going to have to redirect the touch event to your view controller. The most common way of doing this would be having a delegate property in your UITableViewCell.

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID276

crom87
  • 1,141
  • 9
  • 18
-1

I ran into a similar problem myself when creating a custom activity indicator from a subclassed UIView. What I did was create the 'show' function (in the subclass) and pass in a UIViewController parameter, like so:

public func play(inView view: UIViewController) {
//Perform action here
view.present(alertController, animated: true, completion: nil)
}

Simply call it in your view controller like so:

CustomClass.play(inView: self)

Hopefully this helps!

szady
  • 125
  • 1
  • 1
  • 9
  • 1
    This is a really bad approach to this problem. Presentation is a UIViewController's responsibility, not the subview's. The biggest issue with passing the ViewController as argument in a subview is that it could create cyclic dependencies with memory leaks, that are difficult to debug. The delegation pattern, although more cumbersome, is much more safe and native to the iOS environment (although I would personally use a reactive framework). – TheoK Mar 09 '18 at 18:45
  • Hm..thanks for the info/insight. I havent seen any memory leaks yet, but I'll definitely re-do my aproach and go with the delegation pattern like suggested. Thanks! – szady Mar 09 '18 at 19:16
  • Generally speaking it is good to think in terms of responsibility, i.e. who is responsible for what. UIViewControllers are responsible for some things, UIViews for others etc. Where responsibilities overlap try to create well defined limits within your code (in terms of what each class is responsible for) – TheoK Mar 09 '18 at 19:24