swiftswiftui

How can I count @ViewBuilder Views in SwiftUI?


I want to to know how can I count my inPutView in this example of code, the code working like this, it takes some views and gave a background color and counts the count of view, thanks for help.

enter image description here

struct ContentView: View {
    var body: some View {
        
        ModelView(inputView: {
            Text("Hello, world!").padding()
            Text("Hello, world!").padding()
        })
        
    }
}

struct ModelView<Content: View>: View {
    
    var inPutView: () -> Content
    
    init(@ViewBuilder inputView: @escaping () -> Content) { self.inPutView = inputView }
    
    var body: some View {
        
        VStack {
            inPutView()
        }
        .background(Color.green)
        
        Text("count of inPutViews: 2").padding() // Here: How can I found out the count of inPutView?
        
    }
}

update:

    struct ContentView: View {
    
    var inputViews: [AnyView] = [AnyView(Text("Hello, world!").padding()), AnyView(Text("Hello, world!").padding())]
    
    var body: some View {
        
        ModelView2(inputViews: inputViews)
        
    }
}

    struct ModelView2: View {
    var inputViews: [AnyView]

    var body: some View {
        VStack {
            ForEach(inputViews.indices, id:\.self) { index in
                inputViews[index]
            }
        }
        .background(Color.green)

        Text("count of inPutViews: \(inputViews.count)")
            .padding()
    }
}

Solution

  • It's not possible to detect the count if individual views inside a @ViewBuilder closure. The @ViewBuilder creates one resulting view and your inPutView is treated as a single view.

    A possible solution is to pass the [AnyView] array as the input of ModelView. But then AnyView doesn't conform to Hashable nor Identifiable, so you can't use it in a ForEach.

    In your case you can create a separate struct conforming to Identifiable:

    struct AnyViewItem: Identifiable {
        let id = UUID()
        let view: AnyView
    }
    

    and populate ModelView with an array of AnyViewItem:

    struct ModelView: View {
        var inputViews: [AnyViewItem]
    
        var body: some View {
            VStack {
                ForEach(inputViews) {
                    $0.view
                }
            }
            .background(Color.green)
    
            Text("count of inPutViews: \(inputViews.count)")
                .padding()
        }
    }
    

    Then, you can use it in your main view like this:

    struct ContentView: View {
        var body: some View {
            ModelView(
                inputViews: [
                    Text("Hello, world!").padding(),
                    Text("Hello, world!").padding(),
                ]
                .map {
                    AnyViewItem(view: AnyView($0))
                }
            )
        }
    }
    

    Alternatively, as suggested in the comments, in this case you can make inputViews an [AnyView] array and iterate through its indices:

    ForEach(inputViews.indices, id: \.self) {
        inputViews[$0]
    }