I am currently trying to set up a convenience architecture for our view redaction. I therefore added protocols that constrain View and ViewModel to certain properties. I then want to implement a view modifier that makes use of these properties.
Let's assume we have the following setup:
protocol ViewModelDriven {
associatedtype ViewModel
var viewModel: ViewModel { get }
}
protocol RedactionProviding {
var isRedacted: Bool { get }
}
extension View where Self: ViewModelDriven, ViewModel: RedactionProviding {
func redact() -> some View {
redacted(reason: viewModel.isRedacted ? .placeholder : [])
}
}
Up to that everything works fine. The modifier uses viewModel
and isRedacted
provided by the protocols.
When I now try to implement a View/ViewModel using this setup, I get the following error when using the modifier: Referencing instance method 'redact()' on 'View' requires that 'some View' conform to 'ViewModelDriven'
struct FooView: View, ViewModelDriven {
typealias ViewModel = FooViewModel
var viewModel: FooViewModel
var body: some View {
EmptyView()
.redact()
}
}
class FooViewModel: RedactionProviding {
var isRedacted: Bool { true }
}
As far as I understand, this is due to the modifier being attached to an opaque some View
.
Does anybody know if and how I can work around that? Is it even intended to do things like that with SwiftUI or am I on the wrong path? Any help appreciated!
I think your intention is something more like this
extension ViewModelDriven where ViewModel : RedactionProviding {
@ViewBuilder func redact<Content>(content: () -> Content) -> some View where Content : View {
switch viewModel.isRedacted {
case true:
Text("Placeholder")
case false:
content()
}
}
}
that way redact
is available within FooView
and not on the complex views that make up the body
.
You can then use it
struct FooView: View, ViewModelDriven {
typealias ViewModel = FooViewModel
var viewModel: ViewModel
var body: some View {
redact {
Text("Hello World!!")
}
}
}
Here is a full example
struct FooView: View, ViewModelDriven {
typealias ViewModel = FooViewModel
var viewModel: ViewModel
var body: some View {
redact {
Text("Hello World!!")
}
}
}
class FooViewModel: RedactionProviding {
var isRedacted: Bool { true }
}
@MainActor
protocol ViewModelDriven {
associatedtype ViewModel
var viewModel: ViewModel { get }
}
protocol RedactionProviding {
var isRedacted: Bool { get }
}
extension ViewModelDriven where ViewModel : RedactionProviding {
@ViewBuilder func redact<Content>(content: () -> Content) -> some View where Content : View {
switch viewModel.isRedacted {
case true:
Text("Placeholder")
case false:
content()
}
}
}
Note that "ViewModelDriven" will present many more challenges with SwiftUI, this is just a means to answer your question and shouldn't be used as an example of efficiency.