UITableviewCell swipe not work, parent collectionView scrolling instead

I have UICollectionView carousel with UITableView nested inside each UICollectionViewCell.

UICollectionView's layout.scrollDirection = .horizontal, so

  1. UITableView scroll up/down works fine
  2. UITableViewCell swipe left/right does not work, but parent UICollectionView scrolling is triggered instead.

How to keep collectionView.isScrollEnabled true, but prioritise UITableViewCell to allow deleting when swiping on it?

Accepted Reply

Succeeded with another way! Thanks for discussion Claude and for always trying to help!


No success with gestureRecognizer:shouldRequireFailureOfGestureRecognizer: despite the fact it checks really a lot (around 15-20) gesture recognizer pairs, so code would be overcomplicated in my case here. But, anyway, I have another reason why it does not work 👇.


I am using tableView(_:commit:forRowAt:) for row deleting and after testing I've realized that gesture from this method does not being detected at all. So even when I block all gestures in all of collectionView, tableView — swipe on cell is still works.


So, I simply tried gestureRecognizerShouldBegin and it did the job. Here is my solution (zoomed it indicates if collectionView is fullscreen or not). The logic is to check if gesture start point is inside any of tableView rows ( with small gap on left/right to keep ability of collectionView scroll when pan from the sides using insetBy(dx:dy:) ).


extension MyCollectionView: UIGestureRecognizerDelegate {
   
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard zoomed, let currentCell = currentCell, gestureRecognizer.view is MyCollectionView else { return true }
        let cellViews = currentCell.myTableView.visibleCells
        let touchPoint = gestureRecognizer.location(in: currentCell.myTableView)
        let cellsAtPoint = cellViews.filter {
            let localPoint = $0.convert(touchPoint, from: currentCell.myTableView)
            return $0.bounds.insetBy(dx: 40, dy: 0).contains(localPoint)
        }
        return cellsAtPoint.count == 0
    }
   
}

Replies

You should try to set shouldRecognizeSimultaneouslyWithGestureRecognizer


    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

Permitting Simultaneous Gesture Recognition

By default, two gesture recognizers cannot recognize their respective gestures at the same time. But suppose, for example, that you want the user to be able to pinch and rotate a view at the same time. You need to change the default behavior by implementing thegestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: method, an optional method of the UIGestureRecognizerDelegate protocol. This method is called when one gesture recognizer’s analysis of a gesture would block another gesture recognizer from recognizing its gesture, or vice versa. This method returns NO by default. Return YES when you want two gesture recognizers to analyze their gestures simultaneously.


Looks interesting, I've tried it and this method really detects 2 gestures, one is swipe from parent collectionView, another one is from tableViewCell. And that's really the two conflicting gestures!

When i return true here, both gestures work at the same time, it looks funny. CollectionView is scrolling, and at the same time Delete buttom is sliding on the tableViewCell.


Now, I need to somehow be able to block one of these two gestures, to let only swipe tableViewCell when needed. Maybe you have any idea how to block one of them?


Thanks!

I did not try it, so that's just an idea.


Could you test which view the gesture is for:


    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        if let theView = gestureRecognizer.view {
            if theView == theCellView { return false }     // May be the inverse: this one true, the other false ?
            if theView == collectionView { return true }
        }
        return true
    }

Thanks, the last suggestion is not working. I never need two gestures working simultaneously. I need only one at the time always or it will cause deleting and scrolling at the same time)


But I think I've found the solution thanks for your direction in the first message. Testing now these two methods, not sure if will work yet:

  • gestureRecognizer:shouldRequireFailureOfGestureRecognizer:
  • gestureRecognizer:shouldBeRequiredToFailByGestureRecognizer:

Yes, according to doc, that should work:


Declaration

optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool

Discussion

This method is called once per attempt to recognize, so failure requirements can be determined lazily and may be set up between recognizers across view hierarchies. Note that returning

true
is guaranteed to set up the failure requirement; returning
false
, on the other hand, isn’t guaranteed to prevent or remove a failure requirement because
otherGestureRecognizer
might make itself a failure requirement by using its own subclass or delegate methods.

Succeeded with another way! Thanks for discussion Claude and for always trying to help!


No success with gestureRecognizer:shouldRequireFailureOfGestureRecognizer: despite the fact it checks really a lot (around 15-20) gesture recognizer pairs, so code would be overcomplicated in my case here. But, anyway, I have another reason why it does not work 👇.


I am using tableView(_:commit:forRowAt:) for row deleting and after testing I've realized that gesture from this method does not being detected at all. So even when I block all gestures in all of collectionView, tableView — swipe on cell is still works.


So, I simply tried gestureRecognizerShouldBegin and it did the job. Here is my solution (zoomed it indicates if collectionView is fullscreen or not). The logic is to check if gesture start point is inside any of tableView rows ( with small gap on left/right to keep ability of collectionView scroll when pan from the sides using insetBy(dx:dy:) ).


extension MyCollectionView: UIGestureRecognizerDelegate {
   
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
        guard zoomed, let currentCell = currentCell, gestureRecognizer.view is MyCollectionView else { return true }
        let cellViews = currentCell.myTableView.visibleCells
        let touchPoint = gestureRecognizer.location(in: currentCell.myTableView)
        let cellsAtPoint = cellViews.filter {
            let localPoint = $0.convert(touchPoint, from: currentCell.myTableView)
            return $0.bounds.insetBy(dx: 40, dy: 0).contains(localPoint)
        }
        return cellsAtPoint.count == 0
    }
   
}