I've got a tableView
inside of a pageViewController
and when swiping on a cell to bring up the option to delete the cell the gesture is only recognized under certain circumstances, say you swiped very quickly and aggressively.
I imagine this is happening because it's not sure whether the swiping gesture is meant for the pageView
or the tableView
. Is there a way to specifically determine where the swipe gesture is happening to enable a nice smooth display of the delete button?
Theory:
Both UIPageViewController
and UITableView
are implemented using UIScrollView
, where UIPageViewController
embeds UIScrollView
and UITableView
is a subclass of UIScrollView
UITableView
also uses a couple of UIPanGestureRecognizer
s to bring in all the magic. One of these is UISwipeActionPanGestureRecognizer
which handles the swipe to delete actions.
This issue is caused because UIPageViewController
s UIPanGestureRecognizer
wins in the conflict with the UITableView
s UISwipeActionPanGestureRecognizer
s.
So we have to some how tell UIPageViewController
to ignore gestures if UITableView
s UISwipeActionPanGestureRecognizer
are in action.
Luckily there is something already provided by UIGestureRecognizer
.
UIGestureRecognizer
's require(toFail otherGestureRecognizer: UIGestureRecognizer)
creates a relation between the two gesture recognizers that will prevent the gesture's actions being called until the other gesture recognizer fails.
So all we had to do is fail UIPageViewControllers
embedded UIScrollview
s panGestureRecognizer
when UITableView
s UISwipeActionPanGestureRecognizer
are triggered.
There are two ways you could achieve this.
Solution 1: Add a new gesture recognizer to table view and Mimic UISwipeActionPanGestureRecognizer
. And make UIPageViewController panGesture require to fail this new gestureRecognizer
Solution 2 (A bit dirty): Make a string comparision to the UITableView
's UISwipeActionPanGestureRecognizer
and make UIPageViewController
panGesture require to fail this new gestureRecognizer
Code:
Solution 1
A helpful utility to get UIPageViewController
s embedded UIScrollView
extension UIPageViewController {
var scrollView: UIScrollView? {
return view.subviews.first { $0 is UIScrollView } as? UIScrollView
}
}
Add the below code to UIViewController
holding the UITableView
and call it from viewDidLoad()
func handleSwipeDelete() {
if let pageController = parent?.parent as? UIPageViewController {
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: nil)
gestureRecognizer.delaysTouchesBegan = true
gestureRecognizer.cancelsTouchesInView = false
gestureRecognizer.delegate = self
tableView.addGestureRecognizer(gestureRecognizer)
pageController.scrollView?.canCancelContentTouches = false
pageController.scrollView?.panGestureRecognizer.require(toFail: gestureRecognizer)
}
}
And finally delegate methods
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let panGesture = gestureRecognizer as? UIPanGestureRecognizer else {
return false
}
let translation = panGesture.translation(in: tableView)
// In my case I have only trailing actions, so I used below condition.
return translation.x < 0
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return otherGestureRecognizer.view == tableView
}
Solution 2 (A bit dirty)
A helpful utility to get UIPageViewController
s embedded UIScrollView
extension UIPageViewController {
var scrollView: UIScrollView? {
return view.subviews.first { $0 is UIScrollView } as? UIScrollView
}
}
Add the below code to UIViewController
holding the UITableView
and call it from viewDidLoad()
func handleSwipeDelete() {
guard let pageController = parent as? UIPageViewController else {
return
}
pageController.scrollView?.canCancelContentTouches = false
tableView.gestureRecognizers?.forEach { recognizer in
let name = String(describing: type(of: recognizer))
guard name == "_UISwipeActionPanGestureRecognizer" else {
return
}
pageController.scrollView?
.panGestureRecognizer
.require(toFail: recognizer)
}
}
I had the same problem. I found a solution that works well.
Put this in your UIPageViewController
's viewDidLoad
func.
if let myView = view?.subviews.first as? UIScrollView {
myView.canCancelContentTouches = false
}
PageViewControllers have an auto-generated subview that handles the gestures. We can prevent these subviews from cancelling content touches. The tableview will be able to capture swipes for the delete button, while still interpreting swipes that fail the tableview's gesture requirements as page swipes. The delete button will show in cases where you hold and swipe or swipe "aggressively."
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With