3

When trying to use a generic type to init/add a view to another SwiftUI view, I get the error Type 'T' has no member 'init'. How can this be solved?

I guess the problem is, that View is just a protocol which only requires a body property and nothing more. But what type could be used instead?

Simple Example:

Trying to use TrippleView<T: View> which should create/show three views of type T. Adding views (RedRectView or BlueCircleView) is no problem. Passing these views as generic parameter fails:

struct SomeView: View {
    var body: some View {
        TrippleView<RedRectView>()
        TrippleView<BlueCircleView>()
    }
}

struct TrippleView<T: View>: View {
    var body: some View {
        VStack {
            // Does NOT work: Type 'T' has no member 'init'
            T()
            T()
            T()
            
            // Works fine
            // RedRectView()
            // BlueCircleView()
            // RedRectView()
        }
    }
}

struct RedRectView: View {...}
struct BlueCircleView: View {...}

EDIT:

The TrippleView is of course just an example. I would like to use the generic view just as any other generic type: To a common base "type" in different scenarios.

For example two versions of a list view which use a generic CellView to display the same data in two different styles / layouts.

Andrei Herford
  • 17,570
  • 19
  • 91
  • 225
  • 1
    What are you actually trying to achieve? Something that automatically creates three instances of a given view and arranges them, or something else? – jrturton Sep 01 '22 at 09:55
  • I have edited the question to point this out. – Andrei Herford Sep 01 '22 at 10:11
  • 1
    How do you expect `T()` to work if all the compiler knows is that `T` conforms to `View`, and the `View` protocol doesn't declare that initializer? You'd need to create your own protocol that conforms to `View` and declare the initializer you want in it, then you can use that as the requirement for `T` and you would know your views can be initialized that way. – EmilioPelaez Sep 01 '22 at 10:26
  • I'm not sure how your example would work - without a specific initializer, you'd be unable to display any data. It seems like you're trying to reinvent view builders? If you really want to go ahead with this you need to declare an additional protocol with whatever initializer you expect your conforming views to have - you can't just say "its a View" and leave it at that. – jrturton Sep 01 '22 at 10:27

1 Answers1

4

You need a ViewBuilder inside your struct to initialize the view. Here's an example,

Struct that takes in generic view and returns a view.

struct TripleView<T: View>: View {
// simple example that takes in one parameter.
var someView: T

init(@ViewBuilder someView: () -> T) {
    self.someView = someView()
}

var body: some View {
    // You can modify your viewes here. 
    someView
        .foregroundColor(.red)
    someView
        .foregroundColor(.blue)
    someView
        .foregroundColor(.green)
    }
}

Or you can take in multiple parameters

struct TripleViewsWithParam<T1: View, T2: View, T3: View>: View {
// you can take as many parameters as you want.
var someView1: T1
var someView2: T2
var someView3: T3

init(@ViewBuilder someView1: () -> T1, @ViewBuilder someView2: () -> T2, @ViewBuilder someView3: () -> T3) {
    self.someView1 = someView1()
    self.someView2 = someView2()
    self.someView3 = someView3()
}

var body: some View {
    VStack {
        someView1
        someView2
        someView3
    }
}
}

Calling the struct in your main view

struct SomeView: View {
var body: some View {
    VStack{
        TripleView {
            Text("Triple View Example")
        }
        TripleViewsWithParam(
            someView1: {
                Text("View 1")
            }, someView2: {
                Text("View 2")
            }, someView3: {
                Text("View 3")
            }
        )
    }

}
}

The result

Lucas Chae
  • 160
  • 7