Swipe to delete on a tableView that is inside a pageViewController
Asked Answered
E

5

13

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?

Ecchymosis answered 18/4, 2016 at 5:28 Comment(1)
You could add an edit button. Then when in edit mode you could disable the page controller swipe gesture (by setting the delegate nil).Lorrainelorrayne
I
20

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)
    }
}
Include answered 22/7, 2019 at 18:20 Comment(3)
Nice man, it works perfectly even for my tableview which is inside a containerview, which is inside another scrollview which is the UIPageViewcontroller's child ViewControllerMelodious
@Melodious Thanks. And feel free to upvote, so this answer could move up the stack and let people find it quick.Include
Works perfectly.Gonnella
R
6

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

Ratcliffe answered 12/8, 2016 at 22:19 Comment(2)
Ive added this to my UIPageViewController viewDidLoad, what happens is sometimes the swipe to delete shows on the tableview, but most of the time it just swipes to the next page....Anciently
Thanks Carter. I've tried your solution. However, there are still times pageViewController over-take the horizontal swipe action away from the child tableView. How can I ensure that when user swipes on the child tableView, the table view is always prioritized to get the gesture first?Irwinirwinn
U
0

You can set delaysContentTouches to false on the tableView itself as well. This solution worked for my collection view's UISlider elements.

See Swift 4.0 code below:

yourTableView.delaysContentTouches = false
Unilocular answered 27/8, 2018 at 20:25 Comment(0)
V
0

I have found a working solution by reassigning UIScrollView's panGestureRecognizer's delegate to my class and ignoring the original delegate when a right-to-left pan is detected. I used method swizzling for that.

Please check my sample project: https://github.com/kambala-decapitator/SwipeToDeleteInsidePageVC

Vaporetto answered 19/4, 2019 at 13:40 Comment(0)
K
0

In case you have a tableView in the first or the last viewController inside of your UIPageViewController maybe think of disabling the bouncing at the end of your UIPageViewController first and then implement this solution.

In case you don't know how to disable the bounce of the UIPageViewController, check out my answer here.

Kirkman answered 7/2, 2020 at 7:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.