Search code examples
iosswiftswiftuiwatchos

SwiftUI - Navigation Link opening the same view multiple times


I have a list in ViewA which has 3 rows. ViewA is a grand-child of the main view(not sure if this info matters, but just providing since I don't know where the actual bug is. Here is how the stack is ContentView -> opens Main Menu View(MMV) -> select an item from MMV and it opens -> Settings View -> select an item from Settings and it opens ViewA).

When I click on a row in ViewA, another view should opens up let's say ViewB. However I am seeing ViewB instantiating 3 times i.e. I see ViewB appearing 3 times on the screen and then it stops at the last instance.

I am not sure why this is happening and what the actual bug is. Here is my code:

ViewA(MenuViewOptions):

struct MenuViewOptions: View {
    @State var destination: MenuViewType?
    var isActive: Binding<Bool> { Binding(get: { destination != nil }, set: { _ in destination = nil } ) }
    
    var body: some View {
        VStack {
            List(SettingsOptions().menuViewSettingsOptions) { item in
                NavigationLink(isActive: isActive, destination: {destination?.view} ) {
                    MenuViewOptionRowView(menuViewItem: item, destination: $destination)
                }
            }
        }.navigationBarTitle("Settings")
    }
}

MenuViewType code:

enum MenuViewType: Int, CaseIterable, Hashable {
    case circularGrid = 0, regularList = 1, capsuleGrid = 2
    
    @ViewBuilder var view: some View {
        switch self {
            case .circularGrid: MainMenuCircularGridViewNew()
            case .regularList: MainMenuListView()
            case .capsuleGrid: MainMenuCapsuleGridView()
        }
    }
}

SettingsOptions().menuViewSettingsOptions code:

struct SettingsOptions {
    // View inside menu view settings
    let menuViewSettingsOptions: [MenuViewSettingsItem] = [
        MenuViewSettingsItem(id: 0, name: "Circular Grid", imageName: "circle.grid.2x2", isSelected: false),
        MenuViewSettingsItem(id: 1, name: "List", imageName: "list.dash", isSelected: false),
        MenuViewSettingsItem(id: 2, name: "Capsule Grid", imageName: "rectangle.grid.2x2", isSelected: false),
    ]
}

MenuViewOptionRowView code

struct MenuViewOptionRowView: View {
    
    let menuViewItem: MenuViewSettingsItem
    @ObservedObject var userSettingsStore = UserSettingsStore()
    @Binding var destination: MenuViewType?

    var body: some View {
        HStack {
            Image(systemName: menuViewItem.imageName)
                .circularImageStyle(width: 15, height: 15, padding: 5, backgroundColor: Color(red: 34 / 255, green: 34 / 255, blue: 34 / 255), foregroundColor: Color(red: 23 / 255, green: 121 / 255, blue: 232 / 255))
            Text(menuViewItem.name)
            Spacer()
            if menuViewItem.id == userSettingsStore.menuViewSelection {
                Image(systemName: "checkmark").foregroundColor(Color(red: 23 / 255, green: 121 / 255, blue: 232 / 255))
            }
        }.onTapGesture {
            userSettingsStore.menuViewSelection = self.menuViewItem.id
            destination = MenuViewType(rawValue: self.menuViewItem.id)
        }
    }
}

Basically I am setting the destination in MenuViewOptionRowView and once it's set, isActive in MenuViewOptions becomes true and NavigationLink gets fired. I am seeing the row navigates to the correct view, but just that the view instantiates 3 times which I am not sure if it is because I have 3 rows in the list.

If you have any idea of what could be the issue, please do let me know. New to SwiftUI.

Thanks!


Solution

  • You set destination once, but with this you activate all links in List, because they are all bound to one isActive.

    With such code design I would propose the following solution: one destination - one link, destination is already set on tap, so... (code in question is not testable so just as-is)

    List(SettingsOptions().menuViewSettingsOptions) { item in
        MenuViewOptionRowView(menuViewItem: item, destination: $destination)
    }
    .background(
        NavigationLink(isActive: isActive, destination: {destination?.view} ) {
            EmptyView()
        }
    )