Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swipe to delete on a tableView that is inside a pageViewController

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?

like image 730
Gibson Smiley Avatar asked Apr 18 '16 05:04

Gibson Smiley


2 Answers

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 UIPanGestureRecognizers to bring in all the magic. One of these is UISwipeActionPanGestureRecognizer which handles the swipe to delete actions.

This issue is caused because UIPageViewControllers UIPanGestureRecognizer wins in the conflict with the UITableViews UISwipeActionPanGestureRecognizers.

So we have to some how tell UIPageViewController to ignore gestures if UITableViews 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 UIScrollviews panGestureRecognizer when UITableViews 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 UIPageViewControllers 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 UIPageViewControllers 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)
    }
}
like image 166
Srinivasan Avatar answered Sep 22 '22 09:09

Srinivasan


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."

like image 43
Carter Medlin Avatar answered Sep 22 '22 09:09

Carter Medlin