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.
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