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