You didn't include it in your question but I guess the error you're getting when going without the stack is the following?
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
The error gives you a good hint of what's going on but in order to understand it, you need to understand the concept of opaque return types. That's how you call the types prefixed with the some
keyword. I didn't see any Apple engineers going deep into that subject at WWDC (maybe I missed the respective talk?), which is why I did a lot of research myself and wrote an article on how these types work and why they are used as return types in SwiftUI.
There is also a detailed technical explanation in another
If you want to fully understand what's going on I recommend reading both.
As a quick explanation here:
General Rule:
Functions or properties with an opaque result type (some Type
)
must always return the same concrete type.
In your example, your body
property returns a different type, depending on the condition:
var body: some View {
if someConditionIsTrue {
TabView()
} else {
LoginView()
}
}
If someConditionIsTrue
, it would return a TabView
, otherwise a LoginView
. This violates the rule which is why the compiler complains.
If you wrap your condition in a stack view, the stack view will include the concrete types of both conditional branches in its own generic type:
HStack<ConditionalContent<TabView, LoginView>>
As a consequence, no matter which view is actually returned, the result type of the stack will always be the same and hence the compiler won't complain.
💡 Supplemental:
There is actually a view component SwiftUI provides specifically for this use case and it's actually what stacks use internally as you can see in the example above:
It has the following generic type, with the generic placeholder automatically being inferred from your implementation:
ConditionalContent<TrueContent, FalseContent>
I recommend using that view container rather that a stack because it makes its purpose semantically clear to other developers.
ConditionalContent
seems to me a either/or type of struct that gets generated from the compiler when interpreting a@ViewBuilder
block. I think that's how ourifs/elses
inside Groups. Stacks, etc are translated. I think so because it yields aView
. In your case, thatif/else
gets translated to aConditionalContent<TabView, LoginView>
. - Matteo PaciniSwiftUI
, so it will take some time to define abest practice
. Code looks good, so go for it! An improvement you could do: have a state in the view to decide whether to show theTabView
orLoginView
, and then mutate that state via a view model - via aBinding
. - Matteo PaciniHStack { ... }
is only used to provide an “outer group” (to make the if-else compile) then you can also useGroup { ... }
instead. - Martin Rif/else
in a@ViewBuilder
block yields aConditionalStatement
at compiler level: i.imgur.com/VtI4yLg.png. - Matteo Pacini