I use FirebaseAnalytics with View.analyticsScreen(type(of: self))
to log my Views. That works fine: when I have a View called MyBeautifulView
, then exactly that is being logged. However instead of me having to import FirebaseAnalytics
every time, I went ahead and created an extension on View
func analyticsScreen() -> some View {
analyticsScreen(name: "\(type(of: self))")
}
I noticed that when using the method above, the output of type(of: self)
is something like
_ConditionalContent<_ConditionalContent<ModifiedContent<ModifiedContent<ModifiedContent<ModifiedContent<ModifiedContent<ModifiedContent<List<Never, TupleView<(Optional<Section<Text, ForEach<Array, Optional, ModifiedContent<ModifiedContent...
I expected it to be the View's name instead. Why is it not showing the name and how can I get my desired behaviour? Is that a feature? (of course I could just pass the struct name via a parameter, but I would like to know if there is a better way).
Also this is not a Firebase issue, which is why I did not label it as such. It also happens if I just do print(type(of: self))
.
self
means different things depending on the context. In
func analyticsScreen() -> some View {
analyticsScreen(name: "\(type(of: self))")
}
self
refers to the value on which you have called analyticsScreen()
. So if you do
Text("Foo").analyticsScreen()
self
will be Text("Foo")
.
If you have a bunch of modifiers before .analyticsScreen()
, it will be the ModifiedContent<...>
mess that you see.
When you use self
in the body
of your view, it refers to whatever value body
has been called on. Obviously, this will always be an instance of your view struct.
To put it simply, this boils down to:
protocol P {
}
extension P {
func f() {
print(self)
}
}
struct Bar: P {
}
struct Foo: P {
func g() {
print(self) // "self" here refers to a different thing from the "self" when calling Bar().f()
Bar().f()
}
}
There is no way around this. A function cannot know where (as in, which type) it has been called from. You need to pass self
as a parameter:
func analyticsScreen(_ view: some View) -> some View {
analyticsScreen(name: "\(type(of: view))")
}
// usage: SomeView().analyticsScreen(self)
I'd also suggest passing the meta type directly so you don't need type(of:)
.
func analyticsScreen(_ type: (some View).Type) -> some View {
analyticsScreen(name: "\(type)")
}
// usage: SomeView().analyticsScreen(Self.self)
If you really hate that parameter, a compromise could be using the file ID:
func analyticsScreen(_ file: String = #fileID) -> some View {
analyticsScreen(name: "\(file)")
}