SwiftKickMobile / SwiftMessages

A very flexible message bar for UIKit and SwiftUI.
MIT License
7.31k stars 744 forks source link

interactiveHide option (UIPanGestureRecognizer) conflicts with UITableView Swipe action #278

Closed Khalphion closed 5 years ago

Khalphion commented 5 years ago

Hello there,

first thanks for providing this nice library. We are using it for a university project. However, we have a problem with using swipe actions in a UITableView (managed by a UIViewController and shown programmatically with a SwiftMessageSegue):

When interactiveHide is enabled, the attached UIPanGestureRecognizer in TopBottomAnimation.swift is interfering with the UITableViewCell swipe. Sometimes with a lot of luck you can trigger the swipe, but it's really unusable in this current state.

Please see attached videos. https://drive.google.com/open?id=1NU7dHibAVX_tR1mhNqy9oYpsVf5SzUbZ

If we disable the option, it works as expected. https://drive.google.com/open?id=1etGY1KyUHzsk3dghk_XZ6JvAihYNarmg

We tried several things already, however, none of these really work.

The following approach here from the Apple Documentation seemed to be correct, however, we don't know which view is set as delegate, as SwiftMessages has a quite complex view hierarchy.

https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers/preferring_one_gesture_over_another

Do you have an idea how to solve this? Thank you in advance!

EDIT here are two links we also checked: https://stackoverflow.com/questions/14337753/conflicting-gesture-recognizers-on-uitableview https://stackoverflow.com/questions/25526171/gestures-conflict-on-uitableview-swipe-to-delete-ios

wtmoose commented 5 years ago

I added a public property TopBottomAnimation.panGestureRecognizer so you can access it and become its delegate. The change is on the head of the master branch.

Here's an example of how you could access it. Hope it helps.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if let segue = segue as? SwiftMessagesSegue,
        let destination = segue.destination as? MyViewController {
        let animator = TopBottomAnimation(style: .bottom)
        destination.dismissPanGestureRecognizer = animator.panGestureRecognizer
        segue.presentationStyle = .custom(animator: animator)
    }
}
Khalphion commented 5 years ago

Hi,

thank you very much for the fast response! We really appreciate it! (Your quick fix means that we even have the problem solved before the sprint's demo, which we didn't expect)

Accessing the TopBottomAnimation.panGestureRecognizer property works, but only with a litte workaround (which is fine for us as it works, however it's not super elegant).

The problem is that the recognizer will be instantiated very late (TopBottomAnimation.install() is called just before the view is presented, I suppose?). Thus the delegate of the recognizer can't be set during the configuration time (e.g. in your code you provided above) as it's not available yet.

We fixed this by keeping a reference to the TopBottomAnimation instance and setting the recognizer's delegate inside the destination view controller at its lifecycle call viewDidAppear.

The issue is resolved for us, please close it on your discretion in case you'd like to fix that late binding within this scope. 👍

Thanks again and keep up the nice work!

wtmoose commented 5 years ago

It was a super easy change, so no problem. Try the latest commit. The gesture recognizer should be available immediately.

Khalphion commented 5 years ago

Thanks a lot, perfect!

// Source View Controller
let segue = SwiftMessagesSegue(identifier: nil, source: self, destination: destination)
let animation = TopBottomAnimation(style: .bottom)
animation.springDamping = 1

presentationStyle = .custom(animator: animation)

if let destination = destination as? UIGestureRecognizerDelegate {
   animation.panGestureRecognizer.delegate = destination
}

segue.perform()
extension DestinationViewController: UIGestureRecognizerDelegate {
     // Do not begin the pan until the swipe fails.
     // https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers/preferring_one_gesture_over_another
     func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                           shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return gestureRecognizer is UISwipeGestureRecognizer && otherGestureRecognizer is UIPanGestureRecognizer
    }

    // https://stackoverflow.com/questions/18369844/get-swipe-to-delete-on-uitableview-to-work-with-uipangesturerecognizer
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
                           shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}