Skip to content

Instantly share code, notes, and snippets.

@JagCesar
Created July 3, 2024 17:57
Show Gist options
  • Save JagCesar/17fa60e25ca196f5cb171d518157ee67 to your computer and use it in GitHub Desktop.
Save JagCesar/17fa60e25ca196f5cb171d518157ee67 to your computer and use it in GitHub Desktop.
SwiftUI view that pushes twice on iOS 18 b2
//
// ContentView.swift
// dev
//
// Created by César Pinto Castillo on 2024-07-03.
//
import SwiftUI
struct ContentView: View {
@State private var pathManager = PathManager()
@State private var selectedTab: Tabs = .first
var body: some View {
TabView(selection: $selectedTab) {
Tab("First", systemImage: "circle", value: .first) {
NavigationStack(path: $pathManager.firstPath) {
NavigationLink(value: NavigationPath.third) {
Text("Open Third")
}
.navigationDestination()
}
}
Tab("Second", systemImage: "square", value: .second) {
NavigationStack(path: $pathManager.secondPath) {
NavigationLink(value: NavigationPath.fourth) {
Text("Open Fourth")
}
.navigationDestination()
}
}
}
}
}
enum Tabs {
case first
case second
}
enum NavigationPath {
case third
case fourth
}
extension View {
func navigationDestination() -> some View {
self.navigationDestination(for: NavigationPath.self) { path in
switch path {
case .fourth:
Text("Fourth")
case .third:
Text("Third")
}
}
}
}
@Observable
class PathManager {
var firstPath: [NavigationPath] = []
var secondPath: [NavigationPath] = []
}
#Preview {
ContentView()
}
@JagCesar
Copy link
Author

JagCesar commented Jul 3, 2024

@claesjacobsson That builds, bot now we have no way of knowing which views have been pushed. With the original code we can check in the array what is the latest object and even pop views.

Sorry if I was unclear, but this is a requirement for our app. We don't want to push a view that is already on top.

@claesjacobsson
Copy link

Ah, I see.

In your code I think the problem is the placement of the .navigationDestination(). Try moving it...

Tab("First", systemImage: "circle", value: .first) {
                NavigationStack(path: $pathManager.firstPath) {
                    NavigationLink(value: NavigationPath.third) {
                        Text("Open Third")
                            .navigationDestination()
                    }

                }
            }
            Tab("Second", systemImage: "square", value: .second) {
                NavigationStack(path: $pathManager.secondPath) {
                    NavigationLink(value: NavigationPath.fourth) {
                        Text("Open Fourth")
                            .navigationDestination()
                    }

                }
            }

@claesjacobsson
Copy link

I can also recommend Paul Hudson's article on persisting navigations paths, if that is needed. https://www.hackingwithswift.com/books/ios-swiftui/how-to-save-navigationstack-paths-using-codable

@JagCesar
Copy link
Author

JagCesar commented Jul 3, 2024

Moving it fixes it, but it defeats the purpose of using .navigationDestination. The idea is that we define it once per navigation stack, not once per navigation link.

@claesjacobsson
Copy link

Yep, seems weird.

@claesjacobsson
Copy link

claesjacobsson commented Jul 3, 2024

However, in a more real-world like example where views are separated and the PathManager passed as an environment object, it seems to work...

struct ContentView: View {
    private var pathManager = PathManager()
    @State private var selectedTab: Tabs = .first

    var body: some View {
        TabView(selection: $selectedTab) {
            Tab("First", systemImage: "circle", value: .first) {
                FirstScreen()
            }
            Tab("Second", systemImage: "square", value: .second) {
                SecondScreen()
            }
        }
        .environment(pathManager)
    }
}

enum Tabs {
    case first
    case second
}

enum AppScreen {
    case third
    case fourth
}

extension View {
    func navigationDestination() -> some View {
        self.navigationDestination(for: AppScreen.self) { path in
            switch path {
            case .fourth:
                Text("Fourth")
            case .third:
                Text("Third")
            }
        }
    }
}

@Observable
class PathManager {
    var firstPath: [AppScreen] = []
    var secondPath: [AppScreen] = []
}

#Preview {
    ContentView()
}


struct FirstScreen: View {
    @Environment(PathManager.self) private var pathManager

    var body: some View {
        @Bindable var pathManager = pathManager
        NavigationStack(path: $pathManager.firstPath) {
            NavigationLink(value: AppScreen.third) {
                Text("Open Third")
            }
            .navigationDestination()
        }
    }
}

struct SecondScreen: View {
    @Environment(PathManager.self) private var pathManager

    var body: some View {
        @Bindable var pathManager = pathManager
        NavigationStack(path: $pathManager.secondPath) {
            NavigationLink(value: AppScreen.fourth) {
                Text("Open Fourth")
            }
            .navigationDestination()
        }

    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment