Search code examples
swiftswiftuiswiftui-navigationlink

How to pass a ForEach variable into a Binding through a NavigationLink


So I have this code:

struct SelectorView: View {

    let levelamt: Int = 50
    var next: Int = 20
    let columns: [GridItem] = [GridItem(), GridItem(), GridItem()]

    var body: some View {
        NavigationStack {
            ZStack {
                Color(red: 0, green: 0, blue: 0).ignoresSafeArea()
                LazyVGrid(columns: columns) { 
                    ForEach(1..<next, idx: \.self) { x in
                        NavigationLink(destination: GameView(level: self.x), label: {
                            ZStack {
                                Image("Fragment")
                                    .resizable()
                                    .frame(width: 125, height: 125)
                                    .scaledToFit()
                                Text(String(x))
                                    .font(.system(size: 50, design: .monospaced))
                                    .fontWeight(.bold)
                                    .foregroundColor(Color.white)
                                }
                            })
                        }
                    }
                }
            }
        }
    }
}

It's saying that x cannot be passed into GameView's Binding Int level. Can someone please tell me how this works and how to fix it?

Here's the GameView code (it doesn't have anything because I haven't gotten to it yet):

struct GameView: View {

    @Binding var level: Int

    var body: some View {

        Text("hello world")

    }
}

I'm using the legacy version of NavigationLink because I personally feel that it's more convenient to code. When I finish the app, I'll look into it to optimize it. I've also seen the other similar posts but the solutions on those don't work for some reason, maybe because the ForEach is a range and not an array.


Solution

  • ...to pass a ForEach variable into a Binding through a NavigationLink, try this approach using a separate struct GameItem to hold your game levels, and using binding ($) in the ForEach() loop.

    struct GameItem: Identifiable {  // <--- here
        let id = UUID()
        var level: Int
    }
    
    struct SelectorView: View {
        
        let levelamt: Int = 50
        var next: Int = 20
        let columns: [GridItem] = [GridItem(), GridItem(), GridItem()]
        
        // for testing
        @State private var items = [GameItem(level: 0),GameItem(level: 1),GameItem(level: 2)]
        
        var body: some View {
            NavigationStack {
                ZStack {
                    Color(red: 0, green: 0, blue: 0).ignoresSafeArea()
                    LazyVGrid(columns: columns) {
                        ForEach($items) { $item in // <--- here $
                            NavigationLink(destination: GameView(level: $item.level), label: { // <--- here
                                ZStack {
                                    Image("Fragment")
                                        .resizable()
                                        .frame(width: 125, height: 125)
                                        .scaledToFit()
                                    Text(String(item.level)) // <--- here
                                        .font(.system(size: 50, design: .monospaced))
                                        .fontWeight(.bold)
                                        .foregroundColor(Color.white)
                                }
                            })
                        }
                    }
                }
            }
        }
    }
    
    
    struct GameView: View {
        @Binding var level: Int
        
        var body: some View {
            Text("GameView \(level)") 
            Button("Increment level") {  // <--- for testing
                level += 1
            }
        }
    }
    
    struct ContentView: View {
        var body: some View {
            SelectorView()
        }
    }