Here is the idea of what I would like to do: my app's navigation contains a list, which displays a view on tap gesture which is triggered with a ViewModifier
to perform an animation.
In a nutshell:
MyList > MyViewModifier > MyView
Since I want to pass some data from the list to the final view in this hierarchy, I was thinking of using @StateObject
/@EnvironmentObject
property wrappers. But I just realized I can't pass an .environmentObject(xx)
to a ViewModifier like I would do with a View.
Here is the code I have so far:
struct TestModel: Identifiable {
var id: Int
var name: String
//@Published var whateverData I would like to fetch later and see appear in other views?
}
class TestObject: ObservableObject {
@Published var elements: [TestModel] = [
TestModel(id: 1, name: "Element 1"),
TestModel(id: 2, name: "Element 2"),
TestModel(id: 3, name: "Element 3")
]
}
struct MyList: View {
@StateObject var testObject = TestObject()
@State var presenting = false
@State var tappedId: Int?
var body: some View {
List(testObject.elements) { element in
Text(element.name)
.onTapGesture {
tappedId = element.id
presenting = true
}
}
.modifier(
MyViewModifier(presented: $presenting, tappedId: tappedId)
// .environmentObject(testObject) ?
)
}
}
struct MyViewModifier: ViewModifier {
@Binding var presented: Bool
var tappedId: Int?
@EnvironmentObject var testObject: TestObject
func body(content: Content) -> some View {
content.overlay(
ZStack {
if presented {
MyView(testObject: _testObject, tappedId: tappedId)
}
}
)
}
}
struct MyView: View {
@EnvironmentObject var testObject: TestObject
var tappedId: Int?
var body: some View {
ZStack {
Color.white
Text(testObject.elements.first(where: { $0.id == tappedId })?.name ?? "-")
}
.frame(width: 250, height: 250)
}
}
Of course this throws an error when trying to display TestView:
No ObservableObject of type TestObject found. A View.environmentObject(_:) for TestObject may be missing as an ancestor of this view.
(FYI the reason why I want to use an environment object and pass it to the modifier and then the view is that I may want to call an API in TestView
when it appears on screen, which would fetch more information of this object and dispatch this information both in MyList
and MyView
)
What are your recommendations here? Thank you for your help.
As it was suggested in the comments, you could pass the object as a parameter to your ViewModifier
.
Another option is to call .environmentObject()
after .modifier()
and you should be able to read that object from your modifier.
struct MyList: View {
@StateObject var testObject = TestObject()
@State var presenting = false
@State var tappedId: Int?
var body: some View {
List(testObject.elements) { element in
// blah blah
}
.modifier(MyViewModifier(presented: $presenting, tappedId: tappedId))
.environmentObject(testObject)
}
}