Swift : Pull down to dismiss `UITableViewController`
Solution 1
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
}
Solution 2
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
}
Solution 3
Why don't you place print(offsetY) in scrollViewDidScroll. I suspect that (-offsetY) > (tableHeaderHeight+adjustment)
will never be satisfied because of the rubber banding will cause the tableview to rebound before it can dismiss the view controller
Solution 4
For people looking at this in 2019 -- A more modern approach would use the UIGestureRecognizerDelegate methods, instead of keeping extra state in your view controller. For example:
private weak var panFromTop: UIPanGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
// Add pan gesture recognizer
let panFromTop = UIPanGestureRecognizer(target: self, action: #selector(handlePanFromTop(_:)))
panFromTop.delegate = self
tableView.addGestureRecognizer(panFromTop)
self.panFromTop = panFromTop
}
@objc func handlePanFromTop(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .began:
// TODO: BEGIN YOUR ANIMATION HERE
case .changed:
// TODO: UPDATE YOUR ANIMATION HERE
default:
let translation = recognizer.translation(in: view)
let velocity = recognizer.velocity(in: view)
if ((translation.y + velocity.y) / view.bounds.height) > 0.5 {
// TODO: FINISH YOUR ANIMATION HERE
} else {
// TODO: CANCEL YOUR ANIMATION HERE
}
}
}
Disable bounce at the top of the table view only:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.contentOffset.y < 0 {
scrollView.setContentOffset(.zero, animated: false)
}
}
Then implement the gesture recognizer delegate methods:
func gestureRecognizer(
_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
) -> Bool {
return true
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let recognizer = gestureRecognizer as? UIPanGestureRecognizer,
recognizer === panFromTop else {
// Only require special conditions for the panFromTop gesture recognizer
return true
}
// Require the scroll view to be at the top,
// and require the pan to start by dragging downward
return (
tableView.contentOffset.y <= 0 &&
recognizer.velocity(in: view).y > 0
)
}
marrioa
Updated on July 09, 2022Comments
-
marrioa almost 2 years
I want to pull down to dismiss
UITableViewController
so I usedscrollViewDidScroll
method but it didn't works!class CommentViewController: PFQueryTableViewController { private let tableHeaderHeight: CGFloat = 350.0 extension CommentViewController { override func scrollViewDidScroll(scrollView: UIScrollView) { // Pull down to dismiss TVC let offsetY = scrollView.contentOffset.y let adjustment: CGFloat = 130.0 // for later use if (-offsetY) > (tableHeaderHeight+adjustment) { self.dismissViewControllerAnimated(true, completion: nil) } } }
-
marrioa over 8 yearsSo I should delete it ? what do you suggest ?
-
beyowulf over 8 yearsplace print(-offsetY) in the scrollViewDidScroll before the if statement. build and run your app, pull down on your tableview, look at the values in the console. Do they ever become greater than 480.0?
-
marrioa over 8 yearsWhat is eligiblePanOffset ?
-
Eugene Dudnyk over 8 yearsThat is a flag which is true when user pulled enough to the top. You have to compute it basing on panOffset and 0 - point of table view bounds. E.g. let eligiblePanOffset = (panOffset.y <= -300)
-
marrioa over 8 years346.666666666667 this is the result
-
marrioa over 8 yearsI don't know how can you an example
-
beyowulf over 8 yearsSo you need to rethink (-offsetY) > (tableHeaderHeight+adjustment) try (-offsetY)> 340.0
-
marrioa over 8 yearsCrashed After scrolling
panRecognized:]: unrecognized selector sent to instance 0x7fa29b02a770
-
Eugene Dudnyk over 8 yearsmaybe because it's private. Set to public. Let's move to chat conversation.
-
Eugene Dudnyk over 8 yearsLet us continue this discussion in chat.
-
Tim Fuqua over 5 yearsPretty much interchangeable with UIScrollView as well. This solution is much simpler than others I've seen.
-
Thein almost 5 yearsyea, can use with UIScrollView at the same time :) Thanks
-
Muhammad Shauket over 4 yearsAfter doing this tableview doesn’t scroll anymore
-
Eugene Dudnyk over 4 yearsCheck if the method
gestureRecognizer (shouldRecognizeSimultaneouslyWithGestureRecognizer: otherGestureRecognizer:)
is being called for you. I put it there to ensure that the effect that you described doesn't happen. -
Muhammad Shauket over 4 yearsit disable tableview scrolling
-
latenitecoder over 3 yearsCleanest solution on here. Worked out of the box.
-
DawnSong over 3 yearsIf you want to scroll up, you need to drag down a little then drag up.
-
goodliving over 2 yearsGreat answer, thank you for it! To improve it, don't implement
scrollViewDidScroll
but addscrollView.isScrollEnabled = false
tocase .began
andtrue
todefault
inhandlePanFromTop
. -
Leon Jakonda almost 2 yearsbest solution, thx