Open JOyo246 opened 7 months ago
Would casting the result of actualView()
to the expected class work?
E.g., something like:
let wrapperView = try foundWrapper.actualView() as? WrapperView<EmptyView>
let foundWrapperAV = wrapperView?.someProperty
(I’m not in front of Xcode to try this out right now, so the code above might not be exact.)
Would casting the result of
actualView()
to the expected class work?E.g., something like:
let wrapperView = try foundWrapper.actualView() as? WrapperView<EmptyView> let foundWrapperAV = wrapperView?.someProperty
(I’m not in front of Xcode to try this out right now, so the code above might not be exact.)
Late follow up, but this doesn't work. The actualView method still throws with a type mismatch. Which makes sense, as the view that is found is something like WrapperView<VStack<...>>
This is a tricky one. Accessing someProperty
the normal way means the compiler needs to know the exact type of its container (including the inner generics), so without explicit cast to that type it won't let you do .someProperty
.
There is a hacky way though. Try the following:
@testable import ViewInspector // add @testable so you could use internal methods of ViewInspector
let foundWrapper = try sut.find(WrapperView<EmptyView>.self)
let value = try Inspector.attribute(path: "content|view|someProperty", value: foundWrapper, type: Int.self)
Hey @nalexn ! I have a similar issue!
Given a custom container, how can we inspect the generic property content
? In your example, you are using a concrete type Int
.
struct CustomContainer<Content: View>: View {
let content: Content
// ...
}
I'm thinking, this should be possible right since VStack and HStack, etc have a very similar definition?
// SwiftUI VStack
struct VStack<Content> : View where Content : View {}
Also, is there anyway you could briefly explain the path:
argument and how it works? I'm confused in your example by the content|view
prefix?
Thanks so much for your time!
@josh-arnold-1 the internal function Inspector.attribute(path: )
is just a handy wrapper around Swift reflection. The content|view|someProperty
means it'll read property with name content
, on that value read the property with name view
, then someProperty
, and finally cast the resulting value to Int
.
Usually, this is used for reading private properties of SwiftUI views, but in this example, both content
and view
are ViewInspector's internal containers, where view
references the actual SwiftUI view.
If you want to read the property named differently, like in your example, you'd need to change that last path value, making it content|view|content
. I doubt you'll be able to provide the type to cast to though, so instead I'd suggest you use find
instead, on the parent view, to locate the view passed as Content
.
Thanks a lot for the context! How is the body
property of types like VStack resolved in this case? Is it following a similar pattern where you call find
on the VStack for the specific child view, and then work your way up the tree?
E.g, something like this?
let view = AnyView(HStack { Text("abc") })
let text = try sut.inspect().find(text: "abc")
let hStack = try text.parent().hStack()
let anyView = try text.parent().parent().anyView()
I'm wondering if the logic for VStack, for example, could somehow be generalized so it also works for custom generic containers like in my example?
Like, I'm wondering what the difference is between these types?
struct CustomContainer<Content>: View where Content : View {}
struct VStack<Content> : View where Content : View {}
Thanks!
How is the body property of types like VStack resolved in this case?
It also uses Inspector.attribute(path: )
, but without a cast. Casting happens later, as you attempt to unwrap the child view.
Like, I'm wondering what the difference is between these types?
struct CustomContainer<Content>: View where Content : View {}
struct VStack<Content> : View where Content : View {}
The difference is that VStack
is pre-defined in SDK, its structure is fixed and the same for all. It also only references child views.
Custom view can have arbitrary inner structure, reference child views and arbitrary properties. If it's a property outside SwiftUI hierarchy, like Int
property from the original question, the way to access it is described in my original answer. You can as well use Inspector.attribute(path: )
for reaching child view, its' child view, etc., but it's error-prone. That's why I suggest you use find
because it takes all the complexity away.
Alternatively you can introduce a protocol without generics, like
protocol MyCustomContainerView: View {
var contentView: Any { get }
}
conform to that: struct CustomContainer<Content>: MyCustomContainerView, View { ... }
, and then use that protocol in inspection chain instead of .view(CustomContainer.self)
. Haven't tested, lmk if this works
Considering some view that takes in content
We can find a view that is nested in content like so:
But, now it seems as though it's impossible to access someProperty?
let foundWrapperAV = try foundWrapper.actualView().someProperty // fails with type mismatch
Just want to make sure we are not missing anything. we'd like to be able to get the properties of
WrapperView
, ignoring theContent
generic