Skip to content

Use platform-specific navigation transitions - #262

Merged
ninovanhooff merged 5 commits into
mainfrom
261-ios-native-navigation-transition-feel
Nov 10, 2026
Merged

ninovanhooff merged 5 commits into
mainfrom
261-ios-native-navigation-transition-feel

Conversation

@ninovanhooff

@ninovanhooff ninovanhooff commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

Relates to #261

iOS now mimics the UINavigationController push/pop: the new screen slides in over the full width while the screen underneath moves 30% and dims slightly. The back swipe follows the finger, and switching tabs no longer animates, like UITabBarController. Android keeps the existing slide-and-fade.

Why is this important?

Screen transitions didn't feel native: both platforms used the same custom slide-and-fade. iOS users expect the UINavigationController push/pop, and no slide when switching tabs.

  • iOS: the new screen slides in over the full width, while the screen underneath moves 30% (parallax) and dims slightly. The back swipe follows the finger. Switching tabs swaps the screen without animating, like UITabBarController.
  • Android: keeps the existing slide-and-fade.
  • The dim color is a new theme color, navigationTransitionDim.

Notes

  • I found no source saying iOS 27 changes the push/pop animation (the iOS 27 UIKit navigation changes are about bars), so this follows iOS 26.
  • The navigation3 iOS default transition was a starting point, but it dims to 50% black and moves the underlying screen only 25%. This uses values closer to UIKit and keeps its 500 ms timing and easing.
  • Not replicated: the leading-edge shadow and the iOS 26 rounded corners during the transition. The back swipe only starts at the screen edge (a Compose Multiplatform limit), not anywhere in the content as in iOS 26.
  • Tested on the iOS 26.4 Simulator: push, back swipe, and opening the Interop tab. Tapping the native tab bar in the Simulator didn't switch tabs with injected taps; please try it by hand. Android compiles but was not run.

🤖 Generated with Claude Code

iOS now mimics the UINavigationController push/pop: the new screen slides in
over the full width while the screen underneath moves 30% and dims slightly.
The back swipe follows the finger, and switching tabs no longer animates,
like UITabBarController. Android keeps the existing slide-and-fade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ninovanhooff ninovanhooff linked an issue Oct 22, 2026 that may be closed by this pull request
@ninovanhooff

Copy link
Copy Markdown
Collaborator Author

On the dim amount: Apple doesn't document it, but UIKit dims the screen underneath during a push/pop with a private view (_UIParallaxDimmingView). View-debugger inspections report it as black at about 10% opacity in light mode, and barely visible in dark mode. So the 10% (navigationTransitionDim) used here matches native iOS much better than the navigation3 iOS default of 50% black.

@ninovanhooff

Copy link
Copy Markdown
Collaborator Author

I'm not sure this covers the entire scope of #261, but I think it at least closes the gap, so it would be an improvement

@ninovanhooff
ninovanhooff marked this pull request as ready for review October 29, 2026 14:22
Copilot AI balanced review requested due to automatic review settings October 29, 2026 14:22
@ninovanhooff
ninovanhooff requested a review from a team as a code owner October 29, 2026 14:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The predictive-back callback lacks the tab-switch guard, causing back gestures between tab roots to slide and dim despite the intended no-animation policy.

1 open issue
What changed in this PR

Introduces platform-specific navigation transitions for the Compose Multiplatform app, addressing #261 while preserving Android’s existing behavior.

Changes:

  • Adds iOS push/pop transitions with parallax, dimming, and gesture-driven progress.
  • Marks tab destinations to suppress ordinary iOS tab-switch animations.
  • Extracts a shared transition contract and adds a theme-controlled dim color.
File Description
shared/​src/​iosMain/​kotlin/​nl/​q42/​template/​navigation/​NavigationTransitions.ios.kt Implements iOS transitions and tab-switch handling.
shared/​src/​commonMain/​kotlin/​nl/​q42/​template/​navigation/​NavigationTransitions.kt Defines the shared platform transition contract.
shared/​src/​commonMain/​kotlin/​nl/​q42/​template/​navigation/​NavigationRoot.kt Connects platform transitions to NavDisplay.
shared/​src/​commonMain/​kotlin/​nl/​q42/​template/​navigation/​NavEntryForKey.kt Adds tab metadata to navigation entries.
shared/​src/​commonMain/​kotlin/​nl/​q42/​template/​navigation/​BottomBarTab.kt Provides tab metadata and scene detection.
shared/​src/​androidMain/​kotlin/​nl/​q42/​template/​navigation/​NavigationTransitions.android.kt Preserves Android slide-and-fade transitions.
core/​ui/​src/​commonMain/​kotlin/​nl/​q42/​template/​core/​ui/​theme/​AppColorSchemeLight.kt Defines the light-theme transition dim color.
core/​ui/​src/​commonMain/​kotlin/​nl/​q42/​template/​core/​ui/​theme/​AppColorSchemeDark.kt Defines the dark-theme transition dim color.
core/​ui/​src/​commonMain/​kotlin/​nl/​q42/​template/​core/​ui/​theme/​AppColorScheme.kt Adds the navigation dim color to the theme contract.

Review effort: Balanced

@AnneKlapwijk

Copy link
Copy Markdown
Member

On the dim amount: Apple doesn't document it, but UIKit dims the screen underneath during a push/pop with a private view (_UIParallaxDimmingView). View-debugger inspections report it as black at about 10% opacity in light mode, and barely visible in dark mode. So the 10% (navigationTransitionDim) used here matches native iOS much better than the navigation3 iOS default of 50% black.

The dim amount looked good to me. Did you find any documentation from Apple on the other animation specs (speed and motion curve / easing)? Apple Music's exit animation feels a little bit snappier to me when compared 🤔

@AnneKlapwijk AnneKlapwijk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, but maybe fix this weird transition behaviour (also see comment: https://github.com/Q42/GoingsOn.CMP/pull/47#pullrequestreview-5751142214):

ScreenRecording_11-03-2026.12-09-43_1.MP4

@Frank1234
Frank1234 requested a review from roblokhorst November 4, 2026 11:08
ninovanhooff and others added 3 commits November 10, 2026 14:59
Ported from GoingsOn.CMP#47: the predictive back transition now also returns
noTransition() when going back between two tabs, like the regular pop. After
releasing a back swipe, navigation3 finishes in the remaining part of the
predictive duration; 250 ms matches UIKit instead of 500 ms.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ported from GoingsOn.CMP (1e5a6ef1): a back swipe that starts during a push made
NavDisplay seek a new transition while the push was still running, so screens
ended up in the wrong place and jumped to the end. A decorator now disables
back while the entry's transition is running.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ported from GoingsOn: fade only the top screen over the opaque screen underneath.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@ninovanhooff
ninovanhooff merged commit be2b1d1 into main Nov 10, 2026
2 checks passed
@ninovanhooff
ninovanhooff deleted the 261-ios-native-navigation-transition-feel branch November 10, 2026 14:31
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.

iOS-native navigation transition feel

4 participants