diff --git a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/UserInputView.uikit.kt b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/UserInputView.uikit.kt index 3c92c3f58645c..ae32a28b93438 100644 --- a/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/UserInputView.uikit.kt +++ b/compose/ui/ui/src/uikitMain/kotlin/androidx/compose/ui/window/UserInputView.uikit.kt @@ -37,6 +37,8 @@ import platform.CoreGraphics.CGPointMake import platform.CoreGraphics.CGRectZero import platform.UIKit.UIEvent import platform.UIKit.UIGestureRecognizer +import platform.UIKit.UITapGestureRecognizer +import platform.UIKit.UIScreenEdgePanGestureRecognizer import platform.UIKit.UIGestureRecognizerDelegateProtocol import platform.UIKit.UIGestureRecognizerState import platform.UIKit.UIGestureRecognizerStateBegan @@ -135,13 +137,19 @@ private class UserInputGestureRecognizerDelegateProxy : CMPGestureRecognizerDele ): Boolean { // We should recognize simultaneously only with the gesture recognizers // belonging to itself or to the views up in the hierarchy. + // An exception: UIScreenEdgePanGestureRecognizer, this always has precedence over us and is + // not allowed to recognize simultaneously - // Can't check if either view is null + // Can't proceed if either view is null val view = gestureRecognizer.view ?: return false val otherView = otherGestureRecognizer.view ?: return false val otherIsAscendant = !otherView.isDescendantOfView(view) + if (otherIsAscendant && otherGestureRecognizer is UIScreenEdgePanGestureRecognizer) { + return false + } + // Only allow simultaneous recognition if the other gesture recognizer is attached to the same view // or to a view up in the hierarchy return otherView == view || otherIsAscendant @@ -151,10 +159,30 @@ private class UserInputGestureRecognizerDelegateProxy : CMPGestureRecognizerDele gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer ): Boolean { - // We don't require other gesture recognizers to fail. - // Assumption is that we recognize - // simultaneously with the gesture recognizers of the views up in the hierarchy. - // And gesture recognizers down the hierarchy require to failure us. + // Two situations are possible here. + // 1. If it's a gesture recognizer of a descendant (interop) view, + // we should wait until it fails, + // if it's a UITapGestureRecognizer. + // + // 2. It's a gesture recognizer of the view itself, or it's an ascendant view. + // We don't require failure of it, unless it's a `UIScreenEdgePanGestureRecognizer`. + + val view = gestureRecognizer.view ?: return false + val otherView = otherGestureRecognizer.view ?: return false + + val otherIsDescendant = otherView.isDescendantOfView(view) + val otherIsAscendantOrSameView = !otherIsDescendant + + // (1) + if (otherIsDescendant && otherGestureRecognizer is UITapGestureRecognizer) { + return true + } + + // (2) + if (otherIsAscendantOrSameView && otherGestureRecognizer is UIScreenEdgePanGestureRecognizer) { + return true + } + return false } @@ -162,13 +190,24 @@ private class UserInputGestureRecognizerDelegateProxy : CMPGestureRecognizerDele gestureRecognizer: UIGestureRecognizer, otherGestureRecognizer: UIGestureRecognizer ): Boolean { - // Other gesture recognizers, - // except the case where it belongs to the same view, - // are required to wait until we fail. - // In practice, it can only happen when other gesture recognizers are attached to the - // descendant views (aka interop views). - // In other cases, it's allowed to recognize simultaneously, so this method will not be - // called + + // otherGestureRecognizer is UITapGestureRecognizer, + // it must not wait till we fail and has priority + if (otherGestureRecognizer is UITapGestureRecognizer) { + return false + } + + val view = gestureRecognizer.view ?: return false + val otherView = otherGestureRecognizer.view ?: return false + + val otherIsDescendant = otherView.isDescendantOfView(view) + val otherIsAscendantOrSameView = !otherIsDescendant + + if (otherIsAscendantOrSameView && otherGestureRecognizer is UIScreenEdgePanGestureRecognizer) { + return false + } + + // Otherwise it is required to fail (aka other kind of gesture recognizer on interop view) return gestureRecognizer.view != otherGestureRecognizer.view } }