Binding is working differently if used with sheet or navigation link

Viewed 166

I try to use data binding within my view hierarchy. Depending how I call my subview the binding is working as expected.

Working example:

import SwiftUI

struct Person: Identifiable {
    let id: UUID
    var name: String
    var age: Int
}

final class PersonStore: ObservableObject {
    @Published var persons: [Person] = [
        .init(id: .init(), name: "Dirk", age: 28),
        .init(id: .init(), name: "John", age: 31),
        .init(id: .init(), name: "Fred", age: 25)
    ]
}


struct ContentView: View {

    @ObservedObject var store = PersonStore()

    @State var isEditing = false

    var body: some View {
        NavigationView {
            List (store.persons.indices) { index in
                HStack {
                    VStack(alignment: .leading) {
                        Text(store.persons[index].name)
                            .font(.headline)
                        Text("Age: \(store.persons[index].age)")
                            .font(.subheadline)
                            .foregroundColor(.secondary)
                        Text("Index \(index)")
                    }
                    Spacer()
                    Button("Edit", action: {
                        isEditing.toggle()
                    })
                }
                .sheet(isPresented: $isEditing) {
                    NavigationView {
                        EditingView(person: $store.persons[index])
                    }
                }
            }.navigationTitle("Persons")
        }
    }
}

struct EditingView: View {
    @Environment(\.presentationMode) var presentation
    @Binding var person: Person
    
    var body: some View {
        Form {
            Section(header: Text("Personal information")) {
                TextField("type something...", text: $person.name)
                Stepper(value: $person.age) {
                    Text("Age: \(person.age)")
                }
            }
        }
        .navigationBarItems(trailing: saveButton)
        .navigationBarTitle(Text(person.name))
    }
    
    var saveButton: some View {
        Button(action: dismiss) {
            Text("Cancel")
        }
    }
    
    func dismiss() {
        
        self.presentation.wrappedValue.dismiss()
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

When I replace the sheet view with a NavigationLink view, then the binding is broken.

struct ContentView: View {
    
    @ObservedObject var store = PersonStore()
    
    var body: some View {
        NavigationView {
            List (store.persons.indices) { index in
                NavigationLink(destination: EditingView(person: $store.persons[index])) {
                    VStack(alignment: .leading) {
                        Text(store.persons[index].name)
                            .font(.headline)
                        Text("Age: \(store.persons[index].age)")
                            .font(.subheadline)
                            .foregroundColor(.secondary)
                        Text("Index \(index)")
                        
                    }
                }
            }.navigationTitle("Persons")
        }
    }
}

The data in the EditingView is no longer directly updated. Only if I navigate back to the list, the changed data is shown.

Any ideas, where the binding is lost?

1 Answers

You can pass PersonStore as @ObservedObject object directly to subView, with index value parameter, instead of try and handling binding individually for each object. So, any change directly in persons array in PersonStore, will trigger notification to update all views marked with @ObservedObject having PersonStore.

     import SwiftUI
    
    
    struct Person: Identifiable {
        let id: UUID
        var name: String
        var age: Int
    }
    
    final class PersonStore: ObservableObject {
        @Published var persons: [Person] = [
            .init(id: .init(), name: "Dirk", age: 28),
            .init(id: .init(), name: "John", age: 31),
            .init(id: .init(), name: "Fred", age: 25)
        ]
    }
    
    
    struct ContentViewsss: View {
        
        @ObservedObject var store = PersonStore()
        
        var body: some View {
            NavigationView {
                List (store.persons.indices) { index in
                    NavigationLink(destination: EditingView(personStore: store,index:index)) {
                        VStack(alignment: .leading) {
                            Text(store.persons[index].name)
                                .font(.headline)
                            Text("Age: \(store.persons[index].age)")
                                .font(.subheadline)
                                .foregroundColor(.secondary)
                            Text("Index \(index)")
                            
                        }
                    }
                }.navigationTitle("Persons")
            }
        }
    }
    
    struct EditingView: View {
    @Environment(\.presentationMode) var presentation
    @ObservedObject var personStore: PersonStore
    @State private var textValue = ""
    @State private var stepper = 0
    var index:Int

    init(personStore:PersonStore,index:Int) {
        self.personStore = personStore
        self.index = index
    }

    var body: some View {
        Form {
            Section(header: Text("Personal information")) {
                TextField("type something...", text: $personStore.persons[index].name)
                    
                Stepper(value: $personStore.persons[index].age) {
                    Text("Age: \(personStore.persons[index].age)")
                }
            }
        }
        .navigationBarItems(trailing: saveButton)
        .navigationBarTitle(Text(personStore.persons[index].name))
    }

    var saveButton: some View {
        Button(action: dismiss) {
            Text("Cancel")
        }
    }

    
    func dismiss() {

        self.presentation.wrappedValue.dismiss()
    }
}
Related