Search code examples
swiftuiswiftui-navigationlinkswiftui-navigationview

SwiftUI NavigationItem dynamic data sending incorrect parameter


I have what I thought was a simple task, but it appears otherwise. As part of my code, I am simply displaying some circles and when the user clicks on one, it will navigate to a new view depending on which circle was pressed.

If I hard code the views in the NavigationLink, it works fine, but I want to use a dynamic array. In the following code, it doesn't matter which circle is pressed, it will always display the id of the last item in the array; ie it passes the last defined dot.destinationId to the Game view

struct Dot: Identifiable {
    let id = UUID()
    let x: CGFloat
    let y: CGFloat
    let color: Color
    let radius: CGFloat
    let destinationId: Int
}

struct ContentView: View {

var dots = [
    Dot(x:10.0,y:10.0, color: Color.green, radius: 12, destinationId: 1),
    Dot(x:110.0,y:10.0, color: Color.green, radius: 12, destinationId: 2),
    Dot(x:110.0,y:110.0, color: Color.blue, radius: 12, destinationId: 3),
    Dot(x:210.0,y:110.0, color: Color.blue, radius: 12, destinationId: 4),
    Dot(x:310.0,y:110.0, color: Color.red, radius: 14, destinationId: 5),
    Dot(x:210.0,y:210.0, color: Color.blue, radius: 12, destinationId: 6)]

var lineWidth: CGFloat = 1

var body: some View {
    NavigationView {
        ZStack 
            Group {
                ForEach(dots){ dot in
                    NavigationLink(destination: Game(id: dot.destinationId))
                    {
                        Circle()
                            .fill(dot.color)
                            .frame(width: dot.radius, height: dot.radius)
                            .position(x:dot.x, y: dot.y )
                    }
                }
            }
            .frame(width: .infinity, height: .infinity, alignment:  .center  )
            
        }

        .navigationBarTitle("")
        .navigationBarTitleDisplayMode(.inline)
    }
}


struct Game: View {
    var id: Int
    var body: some View {
    
        Text("\(id)")
    }
}

I tried to send the actual view as i want to show different views and used AnyView, but the same occurred It was always causing the last defined view to be navigated to.

I'm really not sure what I a doing wrong, any pointers would be greatly appreciated


Solution

  • While it might seem that you're clicking on different dots, the reality is that you have a ZStack with overlapping views and you're always clicking on the top-most view. You can see when you tap that the dot with ID 6 is always the one actually getting pressed (notice its opacity changes when you click).

    To fix this, move your frame and position modifiers outside of the NavigationLink so that they affect the link and the circle contained in it, instead of just the inner view.

    Also, I modified your last frame since there were warnings about an invalid frame size (note the different between width/maxWidth and height/maxHeight when specifying .infinite).

    struct ContentView: View {
        
        var dots = [
            Dot(x:10.0,y:10.0, color: Color.green, radius: 12, destinationId: 1),
            Dot(x:110.0,y:10.0, color: Color.green, radius: 12, destinationId: 2),
            Dot(x:110.0,y:110.0, color: Color.blue, radius: 12, destinationId: 3),
            Dot(x:210.0,y:110.0, color: Color.blue, radius: 12, destinationId: 4),
            Dot(x:310.0,y:110.0, color: Color.red, radius: 14, destinationId: 5),
            Dot(x:210.0,y:210.0, color: Color.blue, radius: 12, destinationId: 6)]
        
        var lineWidth: CGFloat = 1
        
        var body: some View {
            NavigationView {
                ZStack {
                    Group {
                        ForEach(dots){ dot in
                            NavigationLink(destination: Game(id: dot.destinationId))
                            {
                                Circle()
                                    .fill(dot.color)
                            }
                            .frame(width: dot.radius, height: dot.radius) //<-- Here
                            .position(x:dot.x, y: dot.y ) //<-- Here
                        }
                    }
                    .frame(maxWidth: .infinity, maxHeight: .infinity, alignment:  .center  )
                }
                .navigationBarTitle("")
                .navigationBarTitleDisplayMode(.inline)
            }
        }
    }