Skip to content

Keep iOS swipe-back on the left edge - #481

Open
simonhamp wants to merge 1 commit into
mainfrom
fix/470-edge-swipe-back
Open

simonhamp wants to merge 1 commit into
mainfrom
fix/470-edge-swipe-back

Conversation

@simonhamp

Copy link
Copy Markdown
Member

Fixes #470

On iOS 26 and later, a right swipe that starts anywhere on a pushed screen pops it. That includes the middle of the screen and the right-hand fifth. It takes those swipes away from the app, so swipe-to-reply or a gesture area's @swipe on a pushed screen never fires.

This isn't our code. There is no custom pan in the navigation renderers. iOS 26 added a second pop gesture to UINavigationController, interactiveContentPopGestureRecognizer, and SwiftUI's NavigationStack turns it on. The left-edge swipe is still the older interactivePopGestureRecognizer. I confirmed this with logging on a debug build. A drag that UIKit didn't treat as an edge touch was handled by the recognizer named UINavigationController.contentSwipe. An edge drag was handled by UINavigationController.edgeSwipe.

The change

SwiftUI has no modifier to turn the content gesture off. So I added EdgeOnlySwipeBack, a zero-size UIKit view placed inside each NavigationStack. Once it is in a window it walks up the responder chain to its navigation controller and disables interactiveContentPopGestureRecognizer. Nothing else is touched, so the edge swipe and the back chevron work as before.

It goes on both stacks that can push: the stack chrome renderer and the per-tab stack in the tabs renderer. It is attached at every level, root and pushed, because a stack can open straight onto a pushed screen with its root never on screen. Plain screens without native chrome don't use a navigation stack at all, so they had no swipe-back before this and still don't.

The call sits behind #available(iOS 26.0, *) for older devices. It is also inside #if compiler(>=6.2), the gate #364 proposes for the other iOS 26 calls, so it compiles out on pre-Xcode-26 toolchains instead of adding another build error there.

What to look at

  • The responder walk finds the nearest UINavigationController. Inside tabs that is the tab's own stack, which is what we want.
  • It runs each time any level of the stack enters a window, so a deep link that opens on a pushed screen is covered. Switching it off again is harmless. SwiftUI didn't turn the gesture back on after pushes or re-renders in my testing. A future iOS could change that.

Open question

Should edge-only be configurable? Some apps may want the iOS 26 full-width swipe back. I didn't build an option. This PR makes edge-only the behaviour everywhere, which matches iOS before 26 and Android.

Testing

Test app built from mobile-starter on the iPhone 18 Pro simulator (iOS 27), with a stack layout and a tabs layout. Each pushed screen had a plain area, a gesture area with @swipe, and a gesture area with :pan-x. I ran the same checks on main and on this branch, on a stack push and on a push inside a tab. Results were the same for both.

  • Right flick from the middle: popped on main, stays put now.
  • Right flick from the right-hand fifth: popped on main, stays put now.
  • Right swipe across the @swipe area: popped on main before the swipe could fire. Now it fires right.
  • Drag across the :pan-x area: the card followed the finger on both main and this branch. Its drag grabs the touch first, so the content gesture never won there.
  • Edge swipe: still pops, and the screen follows the finger mid-drag. simdeck's touches don't carry the edge flag, so UIKit's edge recognizer never saw them. I checked this one with a small XCUITest instead, since XCTest touches do trigger it.
  • Back chevron: still pops.
  • Cold-launch deep link straight onto a pushed screen inside a tab: mid-screen and right-fifth flicks don't pop. I couldn't get the stack's root to stay out of the window in this app, because the coordinators push after the first render. So the per-level attachment is a safeguard I couldn't trigger, not a fix for a case I reproduced.

No PHP changed.

🤖 Generated with Claude Code

On iOS 26 and later, a right swipe that started anywhere on a pushed
screen popped it, even from the middle or the right-hand side. That
broke swipe-to-reply and any gesture-area @swipe on pushed screens.

Our navigation code has no custom pan. iOS 26 added a second pop
gesture to UINavigationController, interactiveContentPopGestureRecognizer,
and SwiftUI's NavigationStack turns it on. The left-edge swipe is still
the separate interactivePopGestureRecognizer.

SwiftUI has no modifier to turn the content gesture off, so both
NavigationStacks (stack chrome and the per-tab stack) now carry a
zero-size UIKit view that finds its UINavigationController once it is in
a window and disables that one recognizer. It sits on every level, root
and pushed, so a stack that opens straight onto a pushed screen is
covered too. The edge swipe and the back button are untouched. The call is behind #available(iOS 26.0, *) for
older devices and #if compiler(>=6.2) so it compiles out on pre-Xcode-26
toolchains.

Fixes #470

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 27de7b69-2c1f-486f-8fbb-b181189fa317

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Comment @coderabbitai help to get the list of available commands.

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.

[Masterclass] iOS: right swipe pops back from anywhere on the screen, not just the edge

1 participant