0

This question is identical to SwiftUI @Binding update doesn't refresh view, but the accepted answer is not applicable for my case.

The accepted answer says

A View using a @Binding will update when the underlying @State change, but the @State must be defined within the view hierarchy. (Else you could bind to a publisher)

In my case, the view hierarchy doesn't have the view which is having the @State. The view having the binding is presented modally to the user.

To summarize the issue again

I want to create a view, similar to Toggle which initializes from a Binding. This view will show the contents from the wrapped value and as it performs the updates, the original storage of the value will get updated automatically.

As I have learnt, updating the @Binding in a view, doesn't invalidate it. Then how to implement such a view.

Also I can't depend on the parent view to eventually update this view, because the view is shown on a modally presented screen.

I don't want to use workarounds like using a @State to explicitly trigger a refresh. So what is the correct way to implement such a view.

Code example

The view TextModifier takes a Binding. The view does some modifications to the view. For now it just appends "_Updated" to the value passed.

I initialize the view as TextModifier(text: <some_binding_var>)

struct TextModifier: View {
    @Binding var text: String

    var body: some View {
        Text(text)
            .onTapGesture {
                text += "_Updated"
            }
    }
}

This view shows the text and on tapping it updates it in the original source, but as expected the view doesn't update itself on tapping.

So, how to implement this view so that it also updates itself when it updates the binding value.

The accepted answer to the linked question also says

Else you could bind to a publisher

I don't know how to do this. Does anybody know how to implement this and also provide a code example. Thanks.

Updated with full code and gif

struct ContentView: View {
    @ObservedObject var viewModel = TestViewModel()
    
    var body: some View {
        List {
            ForEach(viewModel.itemsList, id: \.self) { item in
                ItemView(text: $viewModel.itemsList[getItemIndex(item)])
            }
        }
    }
        
    private func getItemIndex(_ item: String) -> Int {
        viewModel.itemsList.firstIndex { $0 == item }!
    }
}

class TestViewModel: ObservableObject {
    @Published var itemsList = ["Item 1", "Item 2", "Item 3"]
}

struct ItemView: View {
    @Binding var text: String
    @State private var showEditorView = false
    
    var body: some View {
        Text(text)
            .onTapGesture {
                showEditorView = true
            }
            .sheet(isPresented: $showEditorView) {
                TextModifier(text: $text, showView: $showEditorView)
            }
    }
}

struct TextModifier: View {
    @Binding var text: String
    @Binding var showView: Bool
    
    var body: some View {
        VStack(spacing: 20) {
            Text("Tap on the text to update it")
                .foregroundColor(.blue)
            Text(text)
                .onTapGesture {
                    text += "_Updated"
                }
            Button {
                showView = false
            } label: {
                Text("Dismiss")
                    .foregroundColor(.blue)
            }

        }
    }
}

enter image description here

  • 1
    Show *some* code of what you have and what problems you're seeing, and/or what you'd like to have. Otherwise, it's just a bunch of guesswork – New Dev Jan 18 '21 at 01:45
  • 1
    A Binding is a two-way connection. It cannot store values the parent of a Binding must be a State or another source of truth. – lorem ipsum Jan 18 '21 at 01:57
  • It is a wrong vision of binding... the answer accepted in provided reference is correct concept usage, there is no workaround there. – Asperi Jan 18 '21 at 02:27
  • @Asperi, you are right the accepted answer is correct and not a workaround. I have updated my question on why the answer doesn't work for me. I should have worded the question in a better way. My bad. Could you please have a look again. Thanks. – Ashish Bansal Jan 18 '21 at 15:29
  • @NewDev, code added and added more context. – Ashish Bansal Jan 18 '21 at 15:57
  • Please add complete parent view of TextModifier and how/to_what you bound it. It looks like there is no dynamic property there so there is nothing to tell SwiftUI rendering engine that attribute graph has changed. `Binding` is passive connection, its purpose is to return value to source of truth, but itself it does nothing, literally. – Asperi Jan 18 '21 at 16:03
  • There is no parent view of the TextModifier. It is presented modally using the sheet modifier. The original answer proposes an approach by binding to a publisher. I don't know what that is and how to do it. – Ashish Bansal Jan 18 '21 at 16:08
  • @AshishBansal, there's always a parent - the view presenting the sheet is the parent. You have to ask yourself: who owns this data? Or, similarly, where is the source of truth? Binding means that an ancestor owns the data - not this view. Which means that somewhere there needs to be a `@State` property or an observable object that holds this data, and when it updates, so will this view. If, on the other hand, you want this view to own the data, then make it into a `@State` property. – New Dev Jan 18 '21 at 18:52
  • @NewDev. I have added the full code with gif. Can you please have a look now. And I have some more observations. In my main content view, if I use a VStack instead of a List, then tapping on the Text in TextModifier automatically (and unintentionally) dismisses the sheet. I have no idea why this is happening. Maybe the problem is different than what I am seeing. – Ashish Bansal Jan 19 '21 at 01:51

0 Answers0