Skip to content

Let a pager report its page once the swipe settles - #135

Draft
simonhamp wants to merge 1 commit into
mainfrom
feat/pager-report-on-settle
Draft

simonhamp wants to merge 1 commit into
mainfrom
feat/pager-report-on-settle

Conversation

@simonhamp

@simonhamp simonhamp commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

The pager reports its page as soon as the page becomes the nearest one during a swipe. That is on purpose. In a media feed, PHP's round trip overlaps the animation and the next video starts loading early.

On a screen with a big frame it hurts. The dev-null app pages between its Log tabs, and each report makes PHP send a frame of about 1,500 nodes. That frame lands in the middle of the swipe and the swipe stutters. With the mid-swipe report taken out, the same swipes were smooth on an iPhone.

This adds an opt-in:

<native:pager horizontal report-on-settle :page="$page" on-page-change="showPage">

reportOnSettle() does the same on the element. Without it nothing changes about when pages are reported.

  • iOS: the scroll geometry callback still tracks the nearest page, but only reports it when the option is off. The idle phase reports either way.
  • Android: reports settledPage instead of currentPage.

Bookkeeping

Both platforms now keep the page PHP last knew about, and that includes a page PHP jumped to with page.

  • iOS didn't count a programmatic jump. With reports on settle, a swipe back to the page before the jump looked like a repeat and went unreported.
  • Android counted jumps, but its echo handling emptied the list on the first render. So it reported the start page to PHP straight after the pager appeared. iOS never did that, and now Android doesn't either.

These two apply with or without the new option. Watch for anything that relied on hearing back about its own jump on iOS, or about the start page on Android.

Tests

PagerTest covers the prop. PagerReportOnSettleTest checks the Swift and Kotlin source, the same way the other iOS tests do. They fail on main.

How I tested

A test app from mobile-starter on an iPhone 17 Pro simulator (iOS 27.0) and an Android 15 emulator. The test screen logs every page report and every render with a timestamp, so I could line them up with the touches.

  • Slow drags of 1 to 3 seconds: the report arrives about 0.2s after the finger lifts, and nothing renders during the drag. Same on both platforms.
  • A jump from PHP to page 2 isn't reported back. A swipe back to page 1 is.
  • A tab tap from Draw a pager's tabs natively, following the swipe #136 scrolls across three pages and reports only the last one.
  • Android: no report at launch any more.

Not tested: a physical device, and a video feed. The default path is the same code as before apart from the bookkeeping above.

The tab strip in #136 (stacked on this one) is the reason this came up. It is separate because this option is useful on its own and is small enough to review in a few minutes.

🤖 Generated with Claude Code

The pager reports the page as soon as it becomes the nearest one mid-swipe, so PHP's round trip overlaps the animation. That is right for a media feed. On a screen whose frame is large, the frame the report causes lands during the animation and the swipe stutters.

report-on-settle (reportOnSettle()) makes the pager report only once the scroll comes to rest. iOS keeps tracking the nearest page but only reports from the idle phase. Android reports settledPage instead of currentPage.

Both platforms now remember the page PHP last knew about, which includes a page PHP jumped to. Without that, on settle, a swipe back to the page before a jump could go unreported on iOS. Android also reported the start page straight after the pager appeared, which iOS never did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
simonhamp added a commit to NativePHP/dev-null-app that referenced this pull request Oct 17, 2026
The tabs above both pagers were drawn in PHP, so they only moved once the pager reported a page and PHP sent a frame. Reporting mid-swipe made that frame land during the animation, which is what made swipes stutter. Reporting only once the swipe settled fixed the stutter but left the tabs about 200ms behind.

Both pagers now draw their own tabs (mobile-ui's tabs attribute) and report on settle. The underline follows your finger, the label you're nearest lights up past halfway, and a tap scrolls the pager there, all on the phone. PHP hears once the pager settles. A tap on the tab you're on comes through as a reselect and takes that page back to its top, as before.

The tabs keep DESIGN.md's look: Geist SemiBold in ink and ink-muted over a 3px blue underline, with the hairline below. The Notifications filters change slightly. Their font="medium" isn't one of the app's font aliases, so they were drawn in the system font; they now match the Log. They also scroll themselves to keep the filter you're on in view, which the PHP row couldn't do.

The error and refresh banners now sit above the tabs, as they do on Profile.

mobile-ui is the dev-null app branch at f197a0a, which adds NativePHP/mobile-ui#135, #136 and #131 (the iOS pager now opens on the tab you last had open).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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