fix: scrolling gets stuck on elements with press-* feedback (iOS 18) - #394
fix: scrolling gets stuck on elements with press-* feedback (iOS 18)#394SRWieZ wants to merge 1 commit into
Conversation
…scrolling The press-* feedback modifier detected touch contact with a zero-distance DragGesture attached via simultaneousGesture. On iOS 18 that drag claims the touch before an enclosing ScrollView can begin its pan, so a scroll that starts on any element with press feedback never starts — screens whose content is mostly pressable cards were only scrollable from the gaps between them. Use _onButtonGesture(pressing:) instead — the recognizer Button itself uses. ScrollView cooperates with it exactly like it does with buttons: pans win, taps still land, and the pressed highlight engages only when the system resolves the touch as a press. The @tap handler is untouched; perform is deliberately empty.
shanerbaner82
left a comment
There was a problem hiding this comment.
Review notes (Air / sim pass)
Diff: One Swift file. Replaces zero-distance DragGesture + simultaneousGesture with _onButtonGesture(pressing:) so ScrollView can share the touch the way a Button does. @tap path left alone; Android untouched. Diagnosis matches the known iOS 18 pressable-vs-scroll failure mode.
Caveat: _onButtonGesture is an underscore SwiftUI API (same family as what Button uses). Fine if we accept that risk, but worth an explicit maintainer call.
Sim demo (iPhone 16 / iOS 18.4): Built the PR repro (dense press-scale cards in a scroll view) on main and on this branch.
I could not reproduce a stuck scroll via Maestro/XCUITest: swipes that start on cards scrolled on both main and this PR. So these clips are not a clean stuck-vs-fixed pair. Finger contact on a real device (or a gesture path that engages the press recognizer) is still the right confirmation.
Media (Drive / Plugins / PRs):
- Folder: https://drive.google.com/drive/folders/17lvl-ERcB3ul4RdYVbNV5K7alT7kO3a7
- Repro top (
main): https://drive.google.com/file/d/1ufCwmuos8v2VFMBueAD_h2bXcigbzGeu/view?usp=drivesdk - After Maestro swipe (
main): https://drive.google.com/file/d/1tZ3j4VFfMs0tew_8Zfk4SC38XbZumsjy/view?usp=drivesdk - After Maestro swipe (this PR): https://drive.google.com/file/d/1oNpriOIXBOh9KPrvbCGvC450lrhiD2_G/view?usp=drivesdk
- Video
mainMaestro swipes (~62s, verified): https://drive.google.com/file/d/13TIzalXXEDNzIRUO1W6h-E0bWCCaCPJC/view?usp=drivesdk - Video this PR Maestro record (~8s, verified): https://drive.google.com/file/d/1ai6KwtMjii-C98jwUG0obmhuo9ctb6D_/view?usp=drivesdk
Verdict from code + prior #374 context: Looks right. Not approving from here — want a finger check on device (or your call on the private API).
— Air (drafted as shanerbaner82)
What's wrong
On iOS 18, a scroll that starts on any element with
press-scale(or anypress-*prop) never scrolls. The press feedback watched for touch contact with a zero-distanceDragGestureattached viasimultaneousGesture; on iOS 18 that drag claims the touch before the surrounding ScrollView can begin its pan. On a screen made mostly of pressable cards, the only way to scroll is to aim for the gaps between them.Minimum repro (fill a screen with these — swipes that start on a card don't scroll):
What this does
_onButtonGesture(pressing:)— the recognizer SwiftUI's ownButtonuses. A ScrollView already knows how to share touches with buttons: pans win, taps still land, and the pressed effect engages only once the system resolves the touch as a press.@tapis untouched (it fires through its own handler). Android is untouched.No screenshot — the bug is a gesture, not pixels; the repro above shows it in seconds on an iOS 18 device.