Skip to content

[tvOS] Detect Siri Remote swipes from absolute pad position - #21

Merged
sajidalidev merged 1 commit into
tvos-mainfrom
tvos-remote-swipe
Sep 7, 2026
Merged

sajidalidev merged 1 commit into
tvos-mainfrom
tvos-remote-swipe

Conversation

@sajidalidev

Copy link
Copy Markdown
Owner

Problem

The current implementation converts UIKit indirect touches spanning more than 40 points (measured in centre-anchored relative space) into synthetic directional key events. This approach has multiple failure modes:

  • Dragging across the ring buttons moves focus instead of being ignored
  • Clicks on the ring buttons or centre pad sometimes generate phantom swipes on the wrong axis
  • Touch suppression uses a one-sided 50 ms per-mediator timestamp, creating unreliable click cancellation
  • Slow finger drifts below detection threshold register as swipes
  • Cannot distinguish between centre pad contact and ring contact

Native tvOS has different semantics: only movement originating on the centre pad counts as a swipe, and button clicks are never also swipes.

Change

A new per-container SiriRemoteTouchOracle polls the GameController micro gamepad for absolute pad position (ranging -1 to 1, with y positive toward the top) while an indirect contact is active. UIKit reports touch position sparsely, so polling at display rate captures finger movement that would otherwise be missed.

Per-contact state machine in ComposeSceneMediator:

  • The swipe origin is the first absolute sample after the touch begins
  • Contacts starting at radius 0.75 or more (ring zone) are ignored entirely
  • A swipe fires once per contact when the dominant axis displacement reaches 0.30 within 250 ms, with the dominant axis at least twice the other
  • A resting finger (under 0.02 movement for 150 ms) resets the origin and timer
  • The swipe dispatch waits 80 ms for a late click to suppress it, or fires immediately on lift
  • Any click or button press within 150 ms before or during the contact cancels the swipe
  • First-generation remotes (no ring) are exempt from the radius gate
  • Without a controller (simulator, first frames) the previous relative-space detection remains as a fallback, with the new thresholds and press gates

The COMPOSE_TVOS_SWIPE_DEBUG=1 environment variable enables detailed decision logging for troubleshooting.

Calibration

Measured on an Apple TV 4K with second-generation Siri Remote:

Finding Value
Centre swipe origins radius 0.33 or less
Ring zone (ignored) radius 0.75 or more
Genuine swipe distance 0.59 or more
Threshold crossing 0.30 within 60-151 ms
Resting drift 0.05 or less
Drift during click up to 0.30 over 300+ ms
Idle pad state (0, 0) exactly

Key constants: CENTER_PAD_RADIUS 0.75, SWIPE_DISTANCE 0.30, SWIPE_MAX_DURATION 250 ms, axis dominance 2x, dispatch hold 80 ms, press window 150 ms, rest reset threshold 0.02 over 150 ms.

Verification

Compiled for tvosArm64 and tvosSimulatorArm64. Tested on device across multiple runs with debug logging:

  • Ring drags produced no focus moves
  • Ring and centre clicks produced exactly their press event, no phantom swipes
  • Centre swipes in all four directions dispatched once each
  • Slow drifts produced no swipes

The final build with the 80 ms dispatch hold is pending a confirmatory device pass. This PR is written assuming that pass succeeds.

Notes

  • Apps that install their own GameController handlers are unaffected, since the oracle only polls and does not consume the value-changed handler
  • The display link runs only while an indirect contact is active, minimizing overhead
  • The demo app is unchanged in this PR

The fork's relative-space detection (UIKit indirect touches over 40 points)
has several issues: it fires on drags across the ring buttons, generates
phantom swipes from clicks, and counts slow drifts as swipes. Native tvOS
only treats pad centre movement as a swipe, and never treats clicks as
swipes.

Switch to a per-container SiriRemoteTouchOracle that polls the GameController
micro gamepad for absolute pad position (-1..1) while an indirect contact is
active. A per-contact state machine fires a swipe once per contact when the
dominant axis displacement reaches 0.30 within 250 ms, with the dominant axis
at least twice the other. Contacts starting at radius 0.75 or more (ring zone)
are ignored. Resting fingers (under 0.02 movement for 150 ms) reset the origin.
A click or button press within 150 ms before or during the contact cancels the
swipe. The dispatch holds for 80 ms to allow a late click to suppress it.
Without a controller (simulator, first frames), fallback to the previous
relative-space detection with the new duration, dead-zone and press gates.

The COMPOSE_TVOS_SWIPE_DEBUG=1 environment variable prints all decisions.
@sajidalidev
sajidalidev merged commit 1d02846 into tvos-main Sep 7, 2026
8 of 17 checks passed
@sajidalidev
sajidalidev deleted the tvos-remote-swipe branch September 7, 2026 16:24
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