You can achieve this without the constructor wrapper by using a combination of:
// define a custom type of environment key for closures
private struct ClosureKey: EnvironmentKey {
static let defaultValue: (() -> Void)? = {}
}
// define a new environment property
extension EnvironmentValues {
var myAction: (() -> Void)? {
get { self[ClosureKey.self]}
set { self[ClosureKey.self] = newValue }
}
}
struct MyActionModifier: ViewModifier {
var perform: (() -> Void)?
func body(content: Content) -> some View {
content.environment(\.myAction, perform)
}
}
struct ChildView: View {
@Environment(\.myAction) var action: (() -> Void)?
var body: some View {
Button(action: {
self.action?()
})
}
func myAction(_ perform: (() -> Void)?) {
self.modifier(MyActionModifier(perform: perform)
}
}
The payoff:
struct ParentView: View {
var body: some View {
ChildView()
.myAction({print("hello world")})
}
}
In reality this is pure madness. I think it's vastly preferable to use an optional closure in the constructor. But perhaps this process can be simplified. After all, declaring ClosureKey
only has to be done once. And perhaps MyActionModifier can be refactored to use a dynamic KeyPath? Which would leave the EnvironmentValues extension as the major task for each custom environment value.
However, I'm not even 100% sure this works yet.
Sources: