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)
}
}