Skip to content

Snap a gesture area's pan, and keep a one-axis pan off the other axis on iOS - #183

Merged
simonhamp merged 1 commit into
mainfrom
feat/gesture-area-snap
Oct 28, 2026
Merged

simonhamp merged 1 commit into
mainfrom
feat/gesture-area-snap

Conversation

@simonhamp

Copy link
Copy Markdown
Member

Two changes to the gesture area's pan, needed for a swipe-to-reveal row that follows the finger (NativePHP/dev-null-app#102).

Snap points

With pan-x-snap / pan-y-snap (from the gesture area's new snap-x / snap-y in mobile-air, branch feat/gesture-area-snap), a bound axis:

  • follows the finger between its lowest and highest point, and a third as far past them;
  • on release springs to the point nearest where a tenth of a second more of the fling would carry it, so a quick flick opens a row the finger only moved part of the way;
  • reports that point in @dragEnd;
  • springs to a PHP write-back (setValue() on a persistent SharedValue) too, so closing a revealed row from PHP slides it shut instead of jumping.

Both platforms use the 350 ms ease-out the bounded pan already settles with. An axis without snap points behaves as before.

iOS: a one-axis pan only takes drags along its axis

A gesture area with only pan-x (or only pan-y) bound, and no bounded or pinch, now uses a UIKit pan that begins only when the finger first moves along that axis. SwiftUI's DragGesture(minimumDistance: 0) claimed every touch from touch-down, so a vertical scroll view couldn't scroll when the drag started on the area. Android's one-axis drag already worked this way (detectHorizontalDragGestures).

  • An enclosing scroll view's pan, and iOS 26's swipe back from mid-screen (interactiveContentPopGestureRecognizer), wait for this pan to decide. A sideways drag on a row moves the row; it doesn't scroll the list or pop the screen.
  • The edge swipe back still wins: the pan waits for interactivePopGestureRecognizer.
  • @dragEnd still reports the unbound axis as the finger's raw movement.

Worth a look

  • Every one-axis pan on iOS now waits for some movement along its axis before it starts, where it used to start on touch-down. That matches Android, but it is a change.
  • Tested on an iPhone 17 Pro simulator (iOS 26.2) and an iPhone 16 Pro in the dev-null app: the row follows the finger, short drags spring back, longer ones or flicks open, a vertical drag starting on a row scrolls the thread, a right drag on your own message doesn't pop the screen, and closing from PHP slides shut. An unsigned Release build compiles.
  • The Android side (GestureAreaRenderer.kt) isn't checked on a device or emulator yet: the snap and write-back code mirrors iOS, and the one-axis drag already existed.

🤖 Generated with Claude Code

… on iOS

A bound pan axis with snap points (pan-x-snap / pan-y-snap, from the
gesture area's snap-x / snap-y) follows the finger between its lowest
and highest point, with resistance past them, and on release springs to
the point nearest where the fling would carry it. @Dragend reports that
point. A PHP write-back on a snapping axis springs there too, so closing
a revealed row from PHP slides it shut. Both platforms use the 350 ms
ease-out the bounded pan already settles with.

On iOS a pan bound on one axis now uses a UIKit pan that only begins
when the finger first moves along that axis. SwiftUI's drag claimed every
touch from touch-down, so a vertical scroll view couldn't scroll when the
drag started on the area. An enclosing scroll view and iOS 26's swipe
back from mid-screen wait for this pan to decide; the edge swipe back
still wins. Android's one-axis drag already behaved this way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@simonhamp
simonhamp merged commit 7cbd248 into main Oct 28, 2026
4 checks passed
@simonhamp
simonhamp deleted the feat/gesture-area-snap branch October 28, 2026 18:54
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