Compose navigation is really what lets iOS down wh...
# compose-ios
m
Compose navigation is really what lets iOS down when building a CMP app. I’m wondering whether I build out my navigation/coordinator flow natively by simply embedding each
ComposeUIViewController
in the
UINavigationController
However it seems @Kashismails has put together a working demo using Voyager. I wonder if anyone else is using a setup like this and in production? https://www.droidcon.com/2024/09/06/using-native-ios-navigation-from-compose-multiplatform/
j
What are the limitations of Compose Navigation that you are running into?
m
It doesn’t feel native. Animations are pretty bad, no amount of tweaking gives the native feel
j
Do they feel native with Voyager?
m
Even the back gesture just feels bad
Well I haven’t given it a go yet. But the tutorial is explaining using native UIKit navigation in the ios source set behind Voyager
j
If I understand, Voyager wraps a SwiftUI NavigationStack and host your composable code?
m
Nope. UIKit UINavigationController which hosts the Composable views through
ComposeUIViewController
j
I can see how this would achieve the “native” iOS navigation animations.. I’m not a fan of resigning navigation constrol to outside of “compose”. I would imagine it would make it harder to tweak and customize but that’s pure speculation on my part… Have you downloaded the 2025 KotinConf app on iOS? It’s build using
NavHost
? The animations look and feel native to my eyes (I’m an Android users so I don’t notice all nuances).
👀 1
m
Just had a look and although the back gesture does work better than my implementation. The animations just don’t feel right! Yup make sense you don’t notice the nuances
j
What is “don’t feel right”?
m
well… doesn’t feel native!
j
Comparing it with the Slack app the overall animation seems correct. An issue that I do notice is when performing a slow/peek “back gesture” navigation. It looks like there is a brief pause of a few frames dragging the “exiting” screen doesn’t seem to run at the correct frame rate 🤷🏿‍♂️. Quickly swiping looks smooth to me. Animating to a new screen looks correct.
1
c
I use this for tab-changing animations in iOS
Copy code
val tabEnter = scaleIn(
    initialScale = .99f, animationSpec = bezierTween(delayMillis = 30)
) + slideInVertically(
    initialOffsetY = { 5 }, animationSpec = bezierTween(delayMillis = 30)
) + fadeIn(
    bezierTween(delayMillis = 30, durationMillis = 30)
)

val tabExit = scaleOut(
    targetScale = .99f, animationSpec = bezierTween(durationMillis = 30)
) + slideOutVertically(
    targetOffsetY = { 5 }, animationSpec = bezierTween(durationMillis = 30)
) + fadeOut(bezierTween(durationMillis = 30, delayMillis = 30))

fun <T> bezierTween(
    durationMillis: Int = 400,
    delayMillis: Int = 0,
) : TweenSpec<T> = tween(
    durationMillis = durationMillis,
    easing = CubicBezierEasing( 0.2833f, 0.99f, 0.31833f, 0.99f),
    delayMillis = delayMillis
)
👀 1
k
I used Voyager for a while with a custom animation. But experienced some bugs which are not easily solved (clearing of viewmodels not always correct). So jumped to Vortex (fork of Voyager which fixes internal stuff like the viewmodels clearing). But still had some stutter while swiping back. Right now I'm trying nav3, which is in alpha. It is much smoother already. Even in debug mode. However, it is still alpha, so I'd wait until it's a little more stable for a production app
👍 1
c
Do not worry too much about alpha/beta versions in androidx libs https://jakewharton.com/you-should-use-androidx-betas/
k
Yes, I'm trying it right now, but on the iOS side there are some bugs, which I just posted in the main channel. Could have nothing to do with nav3, but not sure yet.
👍 2
c
in practice the individual APIs you need might be marked experimental regardless of the library version
m
how does nav3 compare to navigation-compose?
do the animations feel any more 'native'?
I'm leaning more into just exposing all my Compose views through
ComposeUIViewController
and using a Coordinator pattern in my iOS integration:
Copy code
class AppCoordinator: Navigator {
    private let navigationController: UINavigationController
    
    private func navigateToHome() {
        let vc = ComposeViewControllerKt.MainViewController(navigator: self)
        navigationController.pushViewController(vc, animated: true)
    }
    
    func navigateToProfile() {
        let vc = ComposeViewControllerKt.ProfileViewController(navigator: self)
        navigationController.pushViewController(vc, animated: true)
    }
k
I have not used navigation-compose and immediately jumped to Voyager and Vortex, because I don't believe in uri based navigation in apps. But the iOS animations are in my opinion well done and finally really smooth. Android however is just a fade animation, so I recreated the original activity animation in compose. I can imagine using the UINavigationController as it will probably feel a little more native. But going fully compose is just to nice for me to give up. And nav3 makes stuff a lot easier
m
Thought I’d share an example with using Coordinator pattern with
expect/actual
where by the iOS sourceset implements using native `UITabBarController`/`UINavigationController` Personally feels so much better using native navigation (I know that not everyone agrees). There’s also a doc which explains it, though needs some work… https://github.com/markst/weatherdrive-app/blob/main/composeApp/src/iosMain/kotlin/com/weatherdrive/navigation/AppCoordinator.kt
well it turns out there's more official publish docs on this same approach: kotlinlang.org/docs/multiplatform/ios-liquid-glass.html
101 Views