Opaque types explained with code in swift
What are opaque types?
Opaque types allow you to describe the expected return type without defining a concrete type. A common place where we use opaque types today is inside the body of a SwiftUI view:
var body: some View { ... }
At first, it looks like we’re returning a protocol type. Though, the some keyword is crucial here as it allows the compiler to access the actual type information and perform optimizations. For example, the compiler can see the following return types:
var body: VStack { ...
// or:
var body: Text { ... }}
The entire view hierarchy is opaque, allowing the compiler to know the exact size of the returned view body. The compiler can optimize the code, and fewer heap allocations are needed. Without diving into details, you could say we’re giving the compiler more information than just stating we’re expecting a?View?protocol to be returned by appending the some keyword.
Opaque return type without matching underlying types
When writing SwiftUI views, you might run into the following error:
Function declares an opaque return type ‘some View’, but the return statements in its body do not have matching underlying types
An example of code triggering the error looks as follows:
领英推荐
func makeFooterView(isPro: Bool) -> some View {
if isPro {
return Text("Hi there, PRO!") // Return type is Text
} else {
return VStack { // Return type is VStack<TupleView<(Text, Button<Text>)>>
Text("How about becoming PRO?")
Button("Become PRO", action: {
// ..
})
}
}
}
As you can see, we’re returning two types of views: a?VStack?when the?isPro?boolean returns true, otherwise a?Text?view.
As explained before, the compiler wants to know the underlying concrete type through the some keyword. Opaque types need to be fixed for the scope of the value, so we can’t return different types within the same method scope.
We could solve the above code by using a wrapping container, like a?VStack:
func makeFooterView(isPro: Bool) -> some View {
return VStack {
if isPro {
Text("Hi there, PRO!")
} else {
Text("How about becoming PRO?")
Button("Become PRO", action: {
// ..
})
}
}
}
However, we’re now adding an extra container which would only be needed in case the?isPro?returns true.
@ViewBuilde
func makeFooterView(isPro: Bool) -> some View {
if isPro {
Text("Hi there, PRO!")
} else {
VStack {
Text("How about becoming PRO?")
Button("Become PRO", action: {
// ..
})
}
}
}