Say No to AnyView in SwiftUI iOS Applications
SwiftUI
allows developers to use heterogenous view types by making use of AnyView
type. AnyView
encloses any kind of SwiftUI
type into a generic type that can be returned from a function. In this article, we will take a look at,
- Why developers may want to use
AnyView
type in the application - What are the downsides of using it
- How can you refactor your app to not use
AnyView
type
Why developers may want to use AnyView in the application?
AnyView
comes into the picture when you are writing a function that returns heterogenous view types. In that case, you can set your function's return type as generic some View
and cast view to AnyView
before returning it. Let's understand it with an example,
struct CarnivoreView: View {
var body: some View {
Text("Carnivore")
}
}
struct FishView: View {
var body: some View {
Text("Fish")
}
}
struct BirdView: View {
var body: some View {
Text("Bird")
}
}
enum Animal {
case carnivore
case fish
case bird
}
.....
...
func topView(for animal: Animal) -> some View {
switch animal {
case .carnivore:
return AnyView(CarnivoreView())
case .fish:
return AnyView(FishView())
case .bird:
return AnyView(BirdView())
}
}
In the above example, the function topView
returns the view type depending on the type of animal passed to it. Since it could either be CarnivoreView
, FishView
or BirdView
, we are using some View
as a return type and casting them to AnyView
before returning it.
Now that we know why and where AnyView
might get used, let's try to understand some downsides of using it.
What are the downsides of using it?
AnyView
is a type-erasing wrapper type. Using AnyView
hides the if-else structure of code and the underlying concrete type used for building a view. When the compiler looks at the returned type, all it sees is a AnyView
type. This not only makes code hard to read for other developers but can also make it difficult to observe and analyze view structure during debugging.
For example, when the compiler emits useful compile-time diagnostics, using AnyView
makes it hard to understand what's going on under the hood since all it exposes is an AnyView
during the debugging session.
some View
= AnyView
Another downside of using AnyView
is, it also results in poor app performance since SwiftUI's
view-updating framework cannot really determine which underlying view has changed due to type-erasing AnyView
usage. If there is really a need for this use case, please use the generic types to preserve the static type instead of passing AnyView
around.
How can you refactor your app to avoid AnyView type?
Fortunately, it's possible to avoid AnyType
and also to use different view types using a ViewBuilder
construct. ViewBuilder
is an annotation that can be applied to view building functions that can return any type of view without casting it to AnyView
before returning. The return type is still going to be some View
, but it helps preserve underlying type information and the compiler can analyze the internal view structure without losing debugging information.
To apply ViewBuilder
annotation, simply prefix function with @ViewBuilder
annotation and remove all the AnyView
casts inside topView
function.
@ViewBuilder func topView(for animal: Animal) -> some View {
switch animal {
case .carnivore:
CarnivoreView()
case .fish:
FishView()
case .bird:
BirdView()
}
}
When usingViewBuilder
, it is also not necessary to specifyreturn
keyword for returning the view, so we've ommitted it
Now if you analyze the view structure returned by topView
, it will look something like this,
some View = _ConditionalContent<
_ConditionalContent<CarnivoreView, FishView>,
BirdView>
It allows you to inspect what lies inside the view returned by the function and how the choice of final view is evaluated.
Summary
So that's all I wanted to talk about why not to use AnyView
and how to build views using ViewBuilder
in SwiftUI
applications. ViewBuilder offers an excellent way to build reusable views, preserve their internal structure while debugging, and also to improve the app performance by helping update only those underlying views whose data might have changed.
Hopefully, this blog post was helpful for you to understand how to build SwiftUI
apps using ViewBuilder
and build performant iOS apps. If you have any other questions, comments, or feedback, please feel free to reach out on LinkedIn.
Support and Feedback
If you like my blog content and wish to keep me going, please consider donating on Buy Me a Coffee or Patreon. Help, in any form or amount, is highly appreciated and it's a big motivation to keep me writing more articles like this.
Consulting Services
I also provide a few consulting services on Topmate.io, and you can reach out to me there too. These services include,