-1

I have an array of objects presented via a list in a parent view. Each row has a navigation link to a child view that is passed a binding to the object. The child view allows basic editing of the selected item. The child view also contains a button that presents a sheet to a default list of objects the user can "swap" the selected object to.

Swapping the object works, however the child view is only updated after navigating all the way back to the parent.

Am I missing something that is not allowing the child view to update after dismissing the "swap list"?

I do not have this issue when the data is not presented via a list.

Here are some trimmed down examples... The swap list is replaced with a button in the TestEditorView.

Data presented via array:

** child view does not update until navigating back to parent **

struct TestChildBindingList: View {
    @State private var text = ["This is some mutable text", "Please mutate", "Hopefully mutable text"]
    
    var body: some View {
        NavigationStack {
            VStack {
                List {
                    ForEach($text, id: \.self) { $item in
                        NavigationLink(item) {
                            TestChildView(text: $item)
                        }
                    }
                }
            }
            .navigationTitle("Parent View")
        }
    }
}

struct TestChildView: View {
    @Binding var text: String
    @State private var showingChangeText = false
    
    var body: some View {
        VStack {
            Text(text)
            
            Button("Change the text") {
                showingChangeText = true
            }
            .sheet(isPresented: $showingChangeText) {
                TestEditorView(text: $text)
            }
        }
        .navigationTitle("Child View")
    }
}

struct TestEditorView: View {
    @Environment(\.dismiss) var dismiss
    var text: Binding<String>?
    
    var body: some View {
        Button("Press here") {
            text?.wrappedValue = "I pressed the button"
            dismiss()
        }
    }
}

struct TestChildBindingList_Previews: PreviewProvider {
    static var previews: some View {
        TestChildBindingList()
    }
}

Data presented via single object:

** Child view updates correctly without navigating back to parent **

struct TestChildBindingItem: View {
    @State private var text = "This is definately mutable"
    @State private var showingChild = false
    
    var body: some View {
        NavigationStack {
            VStack {
                Text(text)
                
                Button("Show Child View") {
                    showingChild = true
                }
            }
            .sheet(isPresented: $showingChild) {
                TestChildItemView(text: $text)
            }
            .navigationTitle("Parent View")
        }
    }
}

struct TestChildItemView: View {
    @Binding var text: String
    @State private var showingChangeText = false
    
    var body: some View {
        VStack {
            Text(text)
            
            Button("Change the text") {
                showingChangeText = true
            }
            .sheet(isPresented: $showingChangeText) {
                TestEditorView(text: $text)
            }
        }
        .navigationTitle("Child View")
    }
}

struct TestEditorItemView: View {
    @Environment(\.dismiss) var dismiss
    var text: Binding<String>?
    
    var body: some View {
        Button("Press here") {
            text?.wrappedValue = "I pressed the button"
            dismiss()
        }
    }
}

struct TestChildBindingItem_Previews: PreviewProvider {
    static var previews: some View {
        TestChildBindingItem()
    }
}

Thank you for any help offered!

HangarRash
  • 7,314
  • 5
  • 5
  • 32
mmmmmatt
  • 11
  • 3

1 Answers1

2

Don’t use

var text: Binding<String>?

Binding is a property wrapper you have to use it that way.

@Binding var text: String

https://developer.apple.com/documentation/swiftui/binding

Binding as a literal is incapable of telling the body to redraw when needed.

lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
  • I had use var text: Binding? as the editor view is really a list that doesn't always need to take a binding (in my real project). When it does take a binding, the list allows for swapping -otherwise it is static. Not sure how to pass it an optional binding using @Binding so I used var text: Binding? instead. – mmmmmatt May 24 '23 at 23:16
  • However, this also does not explain why the child view updates correctly in my second example instead of the when the bindings are provided to the child via a list (which is how I need them to be supplied). – mmmmmatt May 24 '23 at 23:18
  • @mmmmmatt in theory there is no such thing as a static binding, a binding is by definition a two-way connection. Having a static Binding is just conceptually incorrect. Use a “let”. – lorem ipsum May 24 '23 at 23:24
  • @mmmmmatt List is a lot more selective on when it reloads views to keep from reloading all the rows when it isn’t needed. That is likely why a single variable “works” and not a list, SwiftUI just doesn’t know when to reload. “Demystify SwiftUI” from WWDC breaks down all the mechanics very well. – lorem ipsum May 24 '23 at 23:25
  • Literal Binding as you are using is not a documented use, it isn’t designed to work. – lorem ipsum May 24 '23 at 23:28
  • Changing the edit view to @Binding var text: String from var text: Binding? still does not allow the example to redraw when the binding is updated. – mmmmmatt May 24 '23 at 23:37
  • @mmmmmatt this code doesn’t update or your real code? This code updates fine for me your real code might be breaking some other SwiftUI rule. – lorem ipsum May 25 '23 at 09:33
  • The test code is still broken after changing var text: Binding? to @Binding var text – mmmmmatt May 26 '23 at 18:56