Swift : Pull down to dismiss `UITableViewController`

Swift 4

var panGestureRecognizer : UIPanGestureRecognizer!

override func viewDidLoad() {
    mainTableView.bounces = true
    panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panRecognized))
    panGestureRecognizer.delegate = self
    mainTableView.addGestureRecognizer(panGestureRecognizer)
}


@objc func panRecognized(recognizer: UIPanGestureRecognizer) {
    if recognizer.state == .began && mainTableView.contentOffset.y == 0 {

    } else if recognizer.state != .ended && recognizer.state != .cancelled && recognizer.state != .failed {
        let panOffset = recognizer.translation(in: mainTableView)
        let eligiblePanOffset = panOffset.y > 300
        if eligiblePanOffset {
            recognizer.isEnabled = false
            recognizer.isEnabled = true
            self.dismiss(animated: true, completion: nil)
        }
    }
}


func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

You have to implement additional pan gesture recognizer which will recognize simultaneously with scrollView's pan gesture recognizer. Then you can determine whether user is panning by his finger when table view is already scrolled to the top. e.g.

var isTrackingPanLocation = false
var panGestureRecognizer: UIPanGestureRecognizer!

public override func viewDidLoad() {
    super.viewDidLoad()
    tableView.bounces = false
    panGestureRecognizer = UIPanGestureRecognizer(target: self, 
                                                  action: #selector(panRecognized(gestureRecognizer:)))
    panGestureRecognizer.delegate = self
    tableView.addGestureRecognizer(panGestureRecognizer)
}

public func panRecognized(recognizer: UIPanGestureRecognizer) {
    if recognizer.state == .began && tableView.contentOffset.y == 0 {
        recognizer.setTranslation(CGPoint.zero, inView : tableView)

        isTrackingPanLocation = true
    } else if recognizer.state != .ended && 
              recognizer.state != .cancelled && 
              recognizer.state != .failed && 
              isTrackingPanLocation {
        let panOffset = recognizer.translationInView(tableView)

        // determine offset of the pan from the start here. 
        // When offset is far enough from table view top edge - 
        // dismiss your view controller. Additionally you can 
        // determine if pan goes in the wrong direction and 
        // then reset flag isTrackingPanLocation to false

        let eligiblePanOffset = panOffset.y > 200
        if eligiblePanOffset {
            recognizer.enabled = false
            recognizer.enabled = true
            dismissViewControllerAnimated(true, completion: nil)
        }

        if panOffset.y < 0 {
            isTrackingPanLocation = false
        }
    } else {
        isTrackingPanLocation = false
    }
}

public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, 
    shouldRecognizeSimultaneouslyWithGestureRecognizer 
                    otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}