iosswiftdesign-patternsswift3cocoa-design-patterns

Patterns for Showing a Toast Popup with Custom UITableViewCell


Currently, I have a CustomTableViewCell that is used in four or 5 different places. The custom cell has a lazy loaded UILongPressGestureRecognizer property that gets added as a gesture recognizer in cellForRowAtIndexPath in the parent VC.

self.tableView.addGestureRecognizer(cell.longPress)

When a user initiates the long press, I want a toast notification to popup displaying some contextual information, and then to disappear after a few seconds. I've included this in my code for the CustomTableViewCell, but all of these decisions are starting to "smell." Is there a smarter, more logical way to be implementing these decisions?

This table view cell has the following code:

weak var parentTableView: UITableView?

lazy var longPress: UILongPressGestureRecognizer = {

    let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressSelector))

    longPress.minimumPressDuration = 0.5
    longPress.delegate = self

    return longPress

}()

func longPressSelector(_ longPress: UILongPressGestureRecognizer!) {

    if let tableView = self.parentTableView {

        let point = longPress.location(in: tableView)

        let indexPath = tableView.indexPathForRow(at: point)

        if ((indexPath! as NSIndexPath).section == 0 && longPress.state == .began) {


            // do some work

            // Show informational popup
            let toast = createToastNotification(withTitle: addedSong.name)

            Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { (timer) -> Void in
                UIView.animate(withDuration: 1.0) { () -> Void in
                    toast.alpha = 0.0
                    toast = nil
                }
            }
        }
    }

}

func createToastNotification(withTitle title: String) -> UIView {

    if let tableView = self.parentTableView {
        let windowFrame = tableView.superview?.bounds

        let height:CGFloat = 145, width: CGFloat = 145

        let x = (windowFrame?.width)! / 2 - width / 2
        let y = (windowFrame?.height)! / 2 - height / 2

        let toast = EnsembleToastView.create()

        toast.frame = CGRect(x: x, y: y, width: width, height: height)
        toast.songLabel.text = title
        toast.layer.cornerRadius = 5

        tableView.superview?.addSubview(toast)

        return toast
    }

    return UIView()
}

Solution

  • I think it makes more sense for the TableView to know how to display a toast so I would create a protocol in your tableViewCell, so I would take the following steps.


    So let's do responding to long press gesture first

    protocol TableViewCellLongPressDelegate {
        func tableViewCellHadLongPress(_ cell: YourTableViewCell)
    }
    

    Then extend your TableViewController to conform to your new protocol

    extension YourTableViewController : TableViewCellLongPressDelegate {
        func tableViewCellHadLongPress(_ cell: YourTableViewCell){
             //configure toast based on which cell long pressed
             configureToastNotification(with title: cell.title){
             //show toast
        }
    }
    

    Now, configuring your table view cell within your TableViewController configure your cell and assign the TableViewController as the longPressDelegate

    let cell = YourTableViewCell.dequeue(from: self.tableView)!
    //configure cell
    cell.tableViewCellLongPressDelegate = self
    

    This approach is nice because you can move the createToastNotification() method to your TableViewController and be responsible for creating toast (only once)

    var toastNotification : UIView?
    viewDidLoad(){
        //yatta yatta
        toastNotification = createToastNotification()
    }
    

    Then you can change createToastNotification to

    func createToastNotification() -> UIView? {
    
        let windowFrame = self.bounds
    
        let height:CGFloat = 145, width: CGFloat = 145
    
        let x = (windowFrame?.width)! / 2 - width / 2
        let y = (windowFrame?.height)! / 2 - height / 2
    
        let toast = EnsembleToastView.create()
    
        toast.frame = CGRect(x: x, y: y, width: width, height: height)
        toast.layer.cornerRadius = 5
    
       self.addSubview(toast)
    
        return toast
    }
    

    Lastly for YourTableViewController, configuring toast, let's create a configureToastNotification(with title: String) like:

    func configureToastNotification(with title: String){
        if let toast = self.toastNotification {
            toast.songLabel.text = title
        }
    }
    

    For the end, we remove a lot of the responsibility from YourTableViewCell and allow it to only delegate :

    protocol TableViewCellLongPressDelegate : class {
        func tableViewCellHadLongPress(_ cell: YourTableViewCell)
    }
    
    class YourTableViewCell : UITableViewCell {
    
        //initializers
    
        weak var longPressDelegate: TableViewCellLongPressDelegate?
    
        lazy var longPress: UILongPressGestureRecognizer = {
    
            let longPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressHappened))
    
            longPress.minimumPressDuration = 0.5
            longPress.delegate = self
    
            return longPress
    
        }()
    
        func longPressHappened() {
            self.longPressDelegate?.tableViewCellHadLongPress(self)
        }
    }