Home > other >  How to pass binding to child view in the new NavigationStack.navigationDestination
How to pass binding to child view in the new NavigationStack.navigationDestination

Time:06-12

I am trying to pass a binding from a parent list view into a child detail view. The child detail view contains logic to edit the child. I want these changes to be reflected in the parent list view:

import SwiftUI

struct ParentListView: View {
    var body: some View {
        NavigationStack {
            List {
                ForEach(0 ..< 5) { number in
                    NavigationLink(value: number) {
                        Text("\(number)")
                    }
                }
            }
            .navigationDestination(for: Int.self) { number in
                ChildDetailView(number: number) //Cannot convert value of type 'Int' to expected argument type 'Binding<Int>'

            }
        }
    }
}

struct ChildDetailView: View {
    @Binding var number: Int
    var body: some View {
        VStack {
            Text("\(number)")
            Button {
                number  = 10
            } label: {
                Text("Add 10")
            }
        }
    }
}

But as you can see, I cannot pass number into ChildDetailView because it expects a binding. I have tried putting $ before number but that doesn't work either. Is there a way to make this work, or am I using the new NavigationStack completly wrong?

CodePudding user response:

This might be the solution that you are looking for. When you change the data in the ChildView, the data in the ParentView will be changed too. I used a different NavigationView in ParentView due to not having your package, but it's the same logic.

When you want to pass data between ChildView and ParentView (for small work), you can use @Binding type in ChildView and @State type in ParentView. Code:

import SwiftUI

struct ParentListView: View {

//Try this
@State var bindingData: Int = 0

var body: some View {
    NavigationView {
        List {
            ForEach(0 ..< 5, id: \.self) { _ in
                NavigationLink {
                    ChildDetailView(number: $bindingData) //Try this, and don't forget $ sign
                } label: {
                    Text("\(bindingData)") //Try this
                }
            }
        }
    }
}
}

struct ChildDetailView: View {

@Binding var number: Int //Try this

var body: some View {
    VStack {
        Text("\(number)")
        Button {
            number  = 10
        } label: {
            Text("Add 10")
        }
    }
}
}

CodePudding user response:

Well, actually it is possible, but at first it is needed source of truth, i.e. state with data to bind to, and but in this case Binding will update only source, but not destination. It is more appropriate is to use ObservableObject view model in such case.

Tested with Xcode 14 / iOS 16

Note: binding do not refresh ChildDetailView in such case - in can be used only for actions, but source is updated !!

Here is main part:

@State private var numbers = [1, 2, 3, 4, 5]  // << data !!
var body: some View {
    NavigationStack {
        List {
            ForEach($numbers) { number in      // << binding !!
                NavigationLink(value: number) {
                    Text("\(number.wrappedValue)")
                }
            }
        }
        .navigationDestination(for: Binding<Int>.self) { number in // << !!
            ChildDetailView(number: number)  // << binding !!
        }
    }
}

and a couple of extensions needed for Binding to transfer via navigation link value.

Complete code on GitHub

CodePudding user response:

We did it! Thank you guys so much for your answers, it helped soo much! I was struggling all day with this

class NumbersService: ObservableObject {
    @Published public var numbers = [1, 2, 3, 4, 5]
    
    func incrementByTen(index: Int) {
        numbers[index]  = 10
    }
}

struct ParentListView: View {
    @StateObject var numbersService = NumbersService()
    var body: some View {
        NavigationStack {
            List {
                ForEach(numbersService.numbers.indices, id: \.self) { index in
                    NavigationLink(value: index) {
                        Text("\(numbersService.numbers[index])")
                    }
                }
            }
            .navigationDestination(for: Int.self) { index in
                ChildDetailView(index: index)
            }
        }
        .environmentObject(numbersService)
    }
}

struct ChildDetailView: View {
    @EnvironmentObject var numbersService: NumbersService
    var index: Int

    var body: some View {
        Group {
            Text("\(numbersService.numbers[index])")
            Button {
                numbersService.incrementByTen(index: index)
            } label: {
                Text("Add 10")
            }
        }
    }
}

Basically I just used a shared NumbersService, and identified the selected number by index. This way both views share the same reference to the correct number, and the value correctly updates in BOTH the ParentView and ChildView! I'm not sure if this is way too complicated code, but it works for me :P

  • Related