mica
mica

Reputation: 4308

Having an VStack as a property in a Struct

I need to have a VStack as a property in a struct like this

struct Str
{ 
  let view: VStack
  ....
}

The compiler says:
"Reference to generic type 'VStack' requires arguments in <...>
Insert '<<#Content: View#>>'
"

Xcodes "fix" produces:

struct Str
{
  let view: VStack<Content: View>
  ...
}

But the compiler still complains:
"Expected '>' to complete generic argument list"

Is it allowed to have a VStack in a struct?

Upvotes: 1

Views: 934

Answers (1)

rob mayoff
rob mayoff

Reputation: 385870

What you have discovered is that VStack is not really a type. We can more accurately describe it as a “type constructor”. It is declared like this:

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

You give it some other type as its Content argument, and it gives you a type. You give it the type Text and you get back the type VStack<Text>. You give it the type Image and you get back the type VStack<Image>. The types you get back are different types! A object of type VStack<Text> is not an object of type VStack<Image>.

So you just need to know what type to pass as the argument, right? Well, the problem is that lots of SwiftUI “types” are actually generic type constructors. So the type gets complex. Here's an example from an app I'm working on:

var body: some View {
    VStack(spacing: 0) {
        scene
        if store.model.latestError != nil {
            Divider()
            ErrorView(store: errorStore)
                .padding(20)
        }
    } //
        .frame(width: 450)
}

What's the type of body? I can print it at runtime to find out:

print(type(of: AppView(store: store).body))

The output is

ModifiedContent<VStack<TupleView<(AnyView, Optional<TupleView<(Divider, ModifiedContent<ErrorView, _PaddingLayout>)>>)>>, _FrameLayout>

Notice that the type of the object returned by body exposes lots of details about body's implementation. For example, because the ErrorView has a padding modifier, the body object's type includes ModifiedContent<ErrorView, _PaddingLayout>. I can change the padding amount from 20 to 30 without changing the type. But if I remove the padding modifier entirely, it changes the type (by changing ModifiedContent<ErrorView, _PaddingLayout> to just ErrorView).

So how do you solve this? One way is to use AnyView:

struct Str {
    let view: AnyView
    ...
}

let str = Str(view: AnyView(VStack {
    ...
}))

Note that sometimes you will hear advice that you should avoid AnyView. Here's Joe Groff explaining why:

You can use the AnyView wrapper to explicitly wrap up types where they can dynamically change.

However, the animation system will have an easier time if you can push this down the view graph as far as possible. You could have one Layout type that takes .portrait/.landscape, apply rotation, translation, etc. differently in response, and then the transition can animate

Upvotes: 4

Related Questions