iOS 18 beta bug: NavigationStack pushes the same view twice

Hello, community and Apple engineers. I need your help.

Our app has the following issue: NavigationStack pushes a view twice if the NavigationStack is inside TabView and NavigationStack uses a navigation path of custom Hashable elements.

Our app works with issues in Xcode 18 Beta 13 + iOS 18.0. The same issue happened on previous beta versions of Xcode 18. The issue isn’t represented in iOS 17.x and everything worked well before iOS 18.0 beta releases.

I was able to represent the same issue in a clear project with two simple views. I will paste the code below.

Several notes:

  • We use a centralised routing system in our app where all possible routes for navigation path are implemented in a View extension called withAppRouter().
  • We have a enum RouterDestination that contains all possible routes and is resolved in withAppRouter() extension.
  • We use Router class that contains @Published var path: [RouterDestination] = [] and this @Published property is bound to NavigationStack. In the real app, we need to have an access to this path property for programmatic navigation purposes.
  • Our app uses @ObservableObject / @StateObject approach.
import SwiftUI

struct ContentView: View {
    @StateObject private var router = Router()
 
    var body: some View {
        TabView {
            NavigationStack(path: $router.path) {
                NavigationLink(value: RouterDestination.next, label: {
                                            Label("Next", systemImage: "plus.circle.fill")
                                        })
                        .withAppRouter()
            }
       }
    }
}

enum RouterDestination: Hashable {
    case next
}

struct SecondView: View {
    var body: some View {
       Text("Screen 2")
    }
}

class Router: ObservableObject {
    @Published var path: [RouterDestination] = []
}

extension View {
    func withAppRouter() -> some View {
        navigationDestination(for: RouterDestination.self) { destination in
            switch destination {
            case .next:
                return SecondView()
            }
        }
    }
}

Below you can see the GIF with the issue:

What I tried to do:

  1. Use iOS 17+ @Observable approach. It didn’t help.
  2. Using @State var path: [RouterDestination] = [] directly inside View seems to help. But it is not what we want as we need this property to be @Published and located inside Router class where we can get an access to it, and use for programmatic navigation if needed.

I ask Apple engineers to help with that, please, and if it is a bug of iOS 18 beta, then please fix it in the next versions of iOS 18.0

I found a similar issue reported by other developers here: https://developer.apple.com/forums/thread/759542 so the problem really exists and seems to be a bug

@ulian_onua Please file a bug report via Feedback Assistant

As a workaround please use the type-erased data representation NavigationPath instead:

@Observable
class Router {
    var path: NavigationPath = NavigationPath()
}

@DTS Engineer thank you for your answer.

But what I don't want to use the type-erased data representation and still want to use my custom data type. I need that for correct programmatic routing in my app and for understanding of the exact path of my current router.

There is an Apple's API for that so I expect to use my custom type.

It is still NOT FIXED in Xcode 16 beta 5 + iOS 18.0 beta 5 and the same issue happens.

Any plans to fix it? Thank you.

enum RouterDestination: Hashable {
    case next
}

@DTS Engineer I created and sent Bug report in Feedback assistant: https://feedbackassistant.apple.com/feedback/14743917

Thank you.

@DTS Engineer The issue IS NOT fixed in Xcode 16 Beta 6 + iOS 18 Beta 7 and still persists :(

I have the same issue on iOS beta 7.

Our affected app can't use the workaround, since we need to be able to inspect the navigation path items.

I have filed a radar: FB14936419

Same issue on iOS 18 RC

I stumbled onto a workaround which might be usefull for someone else. I also have the observable Router class with a published NavigationPath, passed along as environment object as described here. I agree that it looks like a bug in iOS 18, the same code works in iOS 17 and also by just removing the tab view. The workaround is to pass the path as a manually created binding:

NavigationStack(path: Binding(get: { navigationPath }, set: { navigationPath = $0 })) {
    ... 
}

Maybe this stops SwiftUI from applying some optimization which is buggy?

With my project built with Xcode 16.0 (16A242d), I experience this same behavior on iOS 18.0. However, the same build on iOS 18.1 beta 5 is not. (actual devices)

So perhaps this is fixed, though I could not find any mention in release notes.

Can anybody confirm the same results?

yes, I can confirm it seems to be fixed in 18.1

iOS 18 beta bug: NavigationStack pushes the same view twice
 
 
Q