Home > Blockchain >  SwfitUI can a function parameter bind multiple same ViewModel instance
SwfitUI can a function parameter bind multiple same ViewModel instance

Time:11-08

extension View {
func showView(show: Binding<Bool>) -> some View {
    ZStack(alignment: .top) {
        self
        if show.wrappedValue {
            Text("Hello, world.")
        }
    }
}
}

class ViewModel: ObservableObject {
    @Published var show: Bool = false
}

struct ContentView: View {

@StateObject var viewModel1 = ViewModel()
@StateObject var viewModel2 = ViewModel()

var body: some View {
    Text("Home")
      .showView(show: $viewModel1.show)
      .showView(show: $viewModel2.show)
}
}

// Question, Is it possiple to merge the 2 modifiers(.showView(show:)) into 1, the binded viewModel1 and viewModel2 is the same type. My purpose is, no matter which view model (viewModel1 or viewModel2 changed), the unique modifier(.showView(show:)) can be invoked to rebuild the view.

CodePudding user response:

First let's recognize that showView doesn't mutate show, so maybe it shouldn't take a Binding at all. Maybe it should take a plain Bool:

extension View {
    func showView(show: Bool) -> some View {
        ZStack(alignment: .top) {
            self
            if show {
                Text("Hello, world!")
            }
        }
    }
}

Then you can use it like this:

Text("Home")
    .showView(show: viewModel1.show || viewModel2.show)

But if you really need it to take a Binding<Bool> because the real implementation does mutate its value, then we can try to write a function that combines two Binding<Bool> like || does for Bool. It doesn't take a lot of code, but I've added a lot of comments that make it look long:

extension Binding where Value == Bool{
    func or(_ other: Self) -> Self {
        return Binding(
            get: { self.wrappedValue || other.wrappedValue },
            set: {
                if $0 {
                    if self.wrappedValue || other.wrappedValue { return }
                    // I'm supposed to make (self || other) be true.
                    // Neither is true right now.
                    // Which one should I set to true? Both? The first? The second?
                    // Neither?
                    //
                    // As written, I set neither, which means I cannot be mutated
                    // from false to true. This is a reasonable behavior.
                    // Built-in SwiftUI modifiers like `alert` and `confirmationSheet`
                    // take a `Binding<Bool>` but only ever set the value to `false`
                    // (to dismiss the pop-up).
                } else {
                    // The only way (self || other) can be false
                    // is if they are both false, so there is no
                    // decision to make. I have to set both to false
                    // to make my own value false.
                    self.wrappedValue = false
                    other.wrappedValue = false
                }
            }
        )
    }
}

You can use it like this:

Text("Home")
    .showView(show: $viewModel1.show.or($viewModel2.show))

But you should study the comment I wrote in the .or method's set. You need to decide what you want it to do. As written, it will do nothing when it's asked to set true. This is a reasonable behavior if showView will only ever set it to false (to dismiss the overlay).

  • Related