skip to Main Content

I’m trying to make an array of Views so that, ideally, each element of the array can have a different composition of child View elements. I have tried to achieve this by creating an array of [some View]. Apparently the compiler inspects the first element of the the array upon initializing the array and expects all elements have a similar composition of subviews. I’d appreciate any help to get around this problem or an alternative solution.

In the simplest form I have created an array of [some View] with two VStack elements, yet each VStack contains two subviews, a Text and an Image with different layout order.

var myArray : [some View] = [
    VStack{
        Text("foo")
        Image("foo_image")
    },
    VStack{
        Image("bar_image")
        Text("bar")
    }
]

Apparently the compiler infers the type of array as () -> TuppleView<Text,Image> by evaluating the first element and complains that the second element is of type () -> TuppleView<Image,Text> can not be converted to the aforesaid type.

My question is that whether there is a way to hide the detail of the containing Views or wrap it in an opaque object so that I can create an array of Views with different elements and layout arrangements.

Here is a minimal reproducible example

struct WrapperView <Content : View> : View , Identifiable{
    var id: String
    let content : Content
    
    var body: some View{
        VStack{
            Text("Some text")
            content
        }
    }
    
    init(id : String , @ViewBuilder content : ()-> Content){
        self.id = id
        self.content = content()
    }
    
}

struct TheItemList {
    
    static var theItems : [WrapperView< some View>] = [
        WrapperView(id : "frist one" , content: {
            HStack{
                Text("foo")
                Image(systemName: "heart")
            }
        }),
        WrapperView(id : "second one" , content: {
            HStack{
                Image(systemName: "bolt")
                Text("bar")
            }
        })
    ]
}

struct TestView : View {
    
    var body: some View{
        ScrollView{
            ForEach(TheItemList.theItems , id: .id){item in
                item
            }
        }
    }
}

struct TestView_Previews: PreviewProvider {
    
    static var previews: some View {
        TestView()
    }
}

Obviously everything is fine when the order of Text and Image elements in the second HStack matches the first one’s.

2

Answers


  1. Chosen as BEST ANSWER

    Problem Solved! Apparently wrapping the view items in AnyView can fix the problem, simple as that!

    var myArray : [some View] = [
        AnyView(VStack{
            Text("foo")
            Image("foo_image")
        }),
        AnyView(VStack{
            Image("bar_image")
            Text("bar")
        })
    ]
    

    and this how it fixes the above mentioned example:

    struct TheItemList  {
        
        static var theItems : [WrapperView< some View>] = [
            WrapperView(id : "frist one" , content: {
                AnyView( HStack{
                    Image(systemName: "heart")
                    Text("foo")
                })
            })
            ,
            WrapperView(id : "second one" , content: {
                AnyView(HStack{
                    Text("bar")
                    Image(systemName: "bolt")
                })
            })
        ]
        
    }
    

  2. The way I would approach something like this…

    You have an array of all sorts of different data. Swift doesn’t do heterogeneous arrays but we can get around this by creating an enum to hold the data…

    enum ContentSection: Identifiable {
      case image(imageName: String)
      case text(String)
      case card(imageName: String, title: String)
      // other cases...
    
      var id: // put an id here
    }
    

    Then in the view you give it an array of these and use that to create the views.

    struct ContentView: View {
      let sections: [ContentSection] = ...
    
      var body: some View {
        VStack {
          ForEach(sections) { section in
            switch section {
              case .image(let imageName):
                Image(systemName: imageName)
              case .text(let text):
                Text(text)
              case let .card(imageName, title):
                MyCardView(imageName: imageName, title: title)
              // other cases...
            }
          }
        }
      }
    }
    

    This will create all your different views based on the type of the enum and the contents held inside each case.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search