Skip to content

Stop the two-finger tap losing its gesture to the pinch - #42

Merged
thirteen37 merged 2 commits into
mainfrom
fix/two-finger-tap-loses-to-pinch
Aug 21, 2026
Merged

thirteen37 merged 2 commits into
mainfrom
fix/two-finger-tap-loses-to-pinch

Conversation

@thirteen37

Copy link
Copy Markdown
Owner

What was wrong

The two-finger right click added in #41 never fired. Pinch and pan both claim two touches the moment they land, and whichever begins first takes the gesture — so the tap was consumed as a zoom of roughly no magnitude. Nothing visibly happened and no click was sent, which matches the reported symptom exactly.

My comment in #41 asserted that "a stationary two-finger tap starts neither the pan nor the pinch — both need movement." That was wrong in practice: fingers drift, and the recognizers claim the touches before the tap can complete.

Trackpad right click is unaffected and confirmed working — it arrives as an indirect secondary click on a different recognizer.

The fix

Pinch and pan now wait for the two-finger tap to fail before they can begin.

Expressed through the shouldRequireFailureOf delegate method rather than require(toFail:). They're equivalent in behaviour, but require(toFail:) has no public getter, so it can only be verified by reading the code — the delegate method can be called directly with the view's real recognizers in a test.

The latency cost is negligible: any real zoom or scroll moves the fingers, which fails the tap at roughly the same threshold those recognizers need to begin anyway. A one-finger tap is deliberately excluded, since it can never block a pinch and waiting on it would only add latency.

What is and isn't verified

  • iPadOS 21/21 pass, including the new test_pinchAndPanWaitForTheTwoFingerTapToFail
  • The test proves the policy is wired up, not that UIKit then lets the tap win. Gesture arbitration can't be exercised without synthesizing real multi-touch input, which needs a UI test target and a host that forces capture on. Confirming this one needs hardware.

Test plan

  • Hardware: two-finger tap sends a right click
  • Hardware: pinch-to-zoom still starts promptly, with no perceptible lag
  • Hardware: two-finger scroll still scrolls
  • Hardware: one-finger drag still starts without delay

🤖 Generated with Claude Code

thirteen37 and others added 2 commits August 21, 2026 23:27
The two-finger right click added in 1.0.6 never fired. Pinch and pan both
claim two touches as soon as they land, and whichever begins first takes the
gesture, so the tap was read as a zoom of roughly no magnitude — nothing
visibly happened and no click was sent.

Pinch and pan now wait for the two-finger tap to fail before they can begin,
expressed through shouldRequireFailureOf rather than require(toFail:) because
the delegate method can be exercised directly in a test. The cost is
negligible: any real zoom or scroll moves the fingers, which fails the tap at
about the same threshold those recognizers need to begin anyway. Only holding
two fingers still defers them, and that is the tap itself.

A one-finger tap is deliberately excluded from the requirement, since it can
never block a pinch and waiting on it would only add latency.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XU7Ck7AbZ9KpRhdPbRzNj8
GestureHarness is a UI-test host that puts the real PointerCaptureView on
screen with capture forced on and surfaces emitted reports in a label.
GestureHarnessUITests drives real multi-touch against it. Recognizer
arbitration cannot be reached from a unit test, and it is where both of the
recent gesture bugs lived.

It immediately paid for itself twice, and once against expectation:

- The first version of this fix made both pan and pinch wait for the tap to
  fail. That starves the pinch completely: a spread fired a right click and
  did not zoom. Caught before shipping.
- Running the harness against the 1.0.6 code showed a pinch there emits a
  right click as well as zooming, because the delegate allowed the tap and
  the pinch to recognize simultaneously. That is a real defect in the shipped
  build, and separate from the reported one.

So the arbitration is now asymmetric: the pan waits for the tap to fail, which
is what makes the tap fire at all; the pinch does not wait and is kept off the
tap by mutual exclusion instead.

Honest limitation: the harness does NOT reproduce the reported bug. The
two-finger tap test passes against the 1.0.6 code, because XCUITest
synthesizes two perfectly simultaneous touches with no drift and real fingers
have plenty. The pan-waits-for-tap fix therefore still rests on inference and
needs hardware confirmation.

The harness has its own scheme and stays out of CI, so PR runs keep their
current shape.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XU7Ck7AbZ9KpRhdPbRzNj8
@thirteen37
thirteen37 merged commit ada4851 into main Aug 21, 2026
3 checks passed
@thirteen37
thirteen37 deleted the fix/two-finger-tap-loses-to-pinch branch August 23, 2026 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant