0

I am trying to get access to my TupleView which my ViewBuilder use it to build the content, then I am trying to count it, here I made my codes until this point, I need help to find the count of my TupleView, right now for unknown reason it returns 0, which I was expecting 5! thanks for help.

    import SwiftUI
struct ContentView: View {
    var body: some View {
        ModelView(content: {
            Text("Hello, world! 1")
                .padding()
            Text("Hello, world! 2")
                .padding()
            Text("Hello, world! 3")
                .padding()
            Text("Hello, world! 4")
                .padding()
            Text("Hello, world! 5")
                .padding()
        })
    }
}

struct ModelView<Content: View>: View {
    var content: () -> Content
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
        let size = Mirror(reflecting: content).children.count   // <<: Here! it returns 0! which must be 5!
        print(size)
    }

    var body: some View {
        content()
    }
}
lorem ipsum
  • 21,175
  • 5
  • 24
  • 48
ios coder
  • 1
  • 4
  • 31
  • 91

2 Answers2

1

First, by accessing Mirror(reflecting: content) you're reflecting the closure that creates Content (you can see that content is actually a closure of type () -> Content).

You probably meant to reflect the view itself. For this you need to reflect the specific instance:

let mirror = Mirror(reflecting: content()) // add the parentheses

Then you tried to access the children. If you access it using the mirror above, you'll get (type formatted for clarity):

AnyCollection<(label: Optional, value: Any)>(
    _box: Swift._RandomAccessCollectionBox<
        Swift.LazyMapSequence<
            Swift.Range<Swift.Int>, 
            (label: Swift.Optional<Swift.String>, value: Any)
        >
    >
)

Theoretically, you could then access the value of the first key (with the label value):

if let value = Mirror(reflecting: content()).children.first?.value {
    print(value)
}

You'd get the result:

(
    SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>(
        content: SwiftUI.Text(
            storage: SwiftUI.Text.Storage.anyTextStorage(
                SwiftUI.unknown context at $7fff57642368.LocalizedTextStorage
            ), modifiers: []
        ),
        modifier: SwiftUI._PaddingLayout(
            edges: SwiftUI.Edge.Set(rawValue: 15),
            insets: nil
        )
    ),
    SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>(...)
)

which is a tuple of type:

(
    SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>,
    SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>,
    SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>,
    SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>,
    SwiftUI.ModifiedContent<SwiftUI.Text, SwiftUI._PaddingLayout>
)

This is probably all you can get through reflection here.


Important note

From documentation:

Mirrors are used by playgrounds and the debugger.

You shouldn't probably use it in the real app.

pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • thanks pawello2222, I think you are so close to answer, how we can count them? – ios coder Feb 01 '21 at 00:06
  • 1
    @swiftPunk I don't think you can. And to be honest I don't see why do you need this information. – pawello2222 Feb 01 '21 at 00:13
  • I am very interested to learn about this topic, If you see I used 5 normal Text, I did not initialized my ModelView with buildBlock in the fact I just used @ViewBuilder, It is kind of magic for me what happing, I called ViewBuilder, but it could find it out that it must use buildBlock, I what to learn what is happening, and if ViewBuilder needs to call buildBlock, it needs the count, so it is easy to find but how? – ios coder Feb 01 '21 at 00:20
  • 1
    @swiftPunk Let’s not make this question *too broad*. This question is about reflection. If you want to learn more about ViewBuilder in general please see [What enables SwiftUI's DSL?](https://stackoverflow.com/q/56434549/8697793) – pawello2222 Feb 01 '21 at 00:36
1

How can I access to TupleView through ViewBuilder in SwiftUI?

Explicitly to the same way as SwiftUI does. Let's see what is buildBlock of the ViewBuilder:

demo1

That means that ViewBuilder has explicit function for every count of content views from 1 to 10. Like

extension ViewBuilder {
    public static func buildBlock<C0, C1, C2, C3>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3) -> TupleView<(C0, C1, C2, C3)> where C0 : View, C1 : View, C2 : View, C3 : View
}

So if you want to access view builder tuple views, you need to do the same, like (tested with Xcode 12.4)

struct ModelContentView: View {
    var body: some View {
        ModelView(content: {
            Text("Hello, world! 1")
                .padding()
            Text("Hello, world! 2")
                .padding()
//            Text("Hello, world! 3")
//                .padding()
//            Text("Hello, world! 4")
//                .padding()
//            Text("Hello, world! 5")
//                .padding()
        })
    }
}

struct ModelView<Content: View>: View {
    var count: Int
    var content: () -> Content

    private init(count: Int = 1, @ViewBuilder content: @escaping () -> Content) {
        self.content = content
        self.count = count
    }

    var body: some View {
        content()
    }
}

extension ModelView {
    init<V1: View, V2: View>(@ViewBuilder content: @escaping () -> Content) where Content == TupleView<(V1, V2)> {
        self.init(count: 2, content: content)
    }

    init<V1: View, V2: View, V3: View>(@ViewBuilder content: @escaping () -> Content) where Content == TupleView<(V1, V2, V3)> {
        self.init(count: 3, content: content)
    }
    
    // ... all other variants
}
Asperi
  • 228,894
  • 20
  • 464
  • 690