Logo for tanaschita.com

How to avoid using AnyView in SwiftUI

Boost SwiftUI performance by using AnyView alternatives.

02 Aug 2021 · 5 min read

SwiftUI provides the type-erased view AnyView that can be used as a wrapper for any other SwiftUI view, for example to be able to return multiple view types from a function.

While using AnyView might be a good choice in some cases, Apple recommends using alternatives when possible.

That’s because SwiftUI uses a so called structural identity mechanism, where SwiftUI uses the view's type to identify it and to determine when they should be updated. Since AnyView erases the type of the view, it reduces SwiftUI’s ability to efficiently update the views.

Using ViewBuilder instead of AnyView

Let's get to an example where we might be tempted to use AnyView.

When defining view properties or functions that return a view, we often use the some View opaque return type so we don't need to explicitly define the exact return type.

private var nameView: some View {
if isEditable {
return TextField("Your name", text: $name)
} else {
return Text(name)
}
}

But the above code won't compile with the error message Function declares an opaque return type, but the return statements in its body do not have matching underlying types.

To resolve the error and return the same type for both views, we might be tempted to wrap them in AnyView.

private var nameView: some View {
if isEditable {
return AnyView(TextField("Your name", text: $name))
} else {
return AnyView(Text(name))
}
}

But there is a better solution to solve the compiler error - the ViewBuilder attribute.

The ViewBuilder attribute allows us to compose multiple views into a single return type.

@ViewBuilder
private var nameView: some View {
if isEditable {
TextField("Your name", text: $name)
} else {
Text(name)
}
}

All we have to do is to add the attribute to our property or function and remove the return statements.

That's the same mechanism the body of a SwiftUI view uses. The only difference is, that we explicitly have to add the ViewBuilder attribute on our own properties and functions.

Using Generics instead of AnyView

Another common situation where we might be tempted to use AnyView is when we would like to store a view without knowing it's type.

struct FlyoutView: View {
let headerView: AnyView
var body: some View {
VStack {
headerView
Spacer()
// other views
}
}
}

In the example above, we want the headerView to be of a flexible type. Since we can’t use the some View opaque return type for stored properties, AnyView seems like a good choice.

A more elegant and performant solution would be using generics.

struct FlyoutView<HeaderView> : View where HeaderView : View {
let headerView: HeaderView
var body: some View {
VStack {
headerView
Spacer()
}
}
}

When creating a FlyoutView, we don't need to wrap the header view in AnyView.

FlyoutView(headerView: Text("Header view"))

The same generics approach is used by many views provided by SwiftUI.

struct VStack<Content> : View where Content : View {
}

As we can see above, the content view of a VStack is of a generic type that conforms to View.

Conclusion

Using AnyView can be avoided in most situations. The explored alternatives did not only add more performance to our SwiftUI views, they also resulted in a more elegant readable code.

Related tags

Written by

Articles with related topics

Latest articles and tips