Constructing a Tree for SwiftUI with custom views

I've been playing with this MirrorView to display SwiftUI Hierarchy in a Tree. It's works pretty great until a custom View is introduced. Then the body type of the View is not shown, only its name.

struct ContentView: View {
    
    var body: some View {
        VStack {
           Text("Lorem Ipsum")
            CustomView()
        }
        .mirror()
    }
    
}

struct CustomView: View {

    var body: some View {
        HStack {
            Text("Hello")
            Text("Word")
        }
    }
    
}

The problem is that the Mirror for ContentView is VStack<TupleView<(Text, CustomView)>>.

I was actually able to solve the problem by propagating the CustomView.Body type through PreferenceKey and replace the occurrence of ContentView by CustomView<HStack<TupleView<(Text, Text)>>>.

The downside of this approach is that I have to manually propagate the body type for every custom view.

struct CustomView: View {

    var body: some View {
        HStack {
            Text("Hello")
            Text("Word")
        }
        .propagateBodyType(self)
    }
    
}

I wonder if there is a way to go from VStack<TupleView<(Text, CustomView)>> to VStack<TupleView<(Text, CustomView<HStack<TupleView<(Text, Text)>>>)>> just using reflection. Does anyone have any ideas?

SwiftUI's ViewBuilder doesn't provide a buildExpression static method, so one option could be to "inject" one yourself:

var customViews: [String: String] = [:]

extension ViewBuilder {
  static func buildExpression<T: View>(_ expression: T) -> T {
    if !(T.Body.self is Never.Type) {
      customViews["\(T.self)"] = "\(T.Body.self)"
    }
    return expression
  }
}

With your code sample, customViews will contain the following:

[
  "CustomView": "HStack<TupleView<(Text, Text)>>",
  "ContentView": "VStack<TupleView<(Text, CustomView)>>"
]

You can then manually (and recursively) find and replace all the occurrences of those view names. It looks like Chris Eidhof's implementation is string-based, so it should suffice.

2 Likes

That’s simple and clever! Thank you! :grinning: