2

I'm trying to implement a markdown renderer using SwiftUI. Markdown document contains a variety of blocks, where a block may be embedded in another block, for example, block quotes:

Quote Level 1

Quote Level 2

Quote Level 3

...

The entire document forms a tree-like structure with arbitrary depth, requiring the renderer to take a recursive approach. I adopted the following code structure:

@ViewBuilder
func renderBlock(block: Block) -> some View {
    switch block {
    // other types of elements
    case let block as BlockQuote:
        HStack {
            GrayRectangle()
            ForEach(block.children) { child in
                renderBlock(child) // recursion
            }
        }
    }
}

However, the compiler rejects that as it require the return type to be determined during compile phase. Is it possible to generate dynamic view structure like this in SwiftUI?

1 Answer 1

1

ViewHierarchy.swift:

 import SwiftUI

 @main
 struct ViewHierarchyApp: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView(tree: mockData())
        }
    }
 }

 func mockData() -> [Tree] {
    let tree2: [Tree] = [Tree(id: 2, title: "2", items: [])]
    let tree1: [Tree] = [Tree(id: 1, title: "1", items: []),
                         Tree(id: 11, title: "11", items: []),
                         Tree(id: 12, title: "12", items: tree2)]
    let tree: [Tree] = [Tree(id: 0, title: "Root", items: tree1)]
    return tree
 }

Model.swift:

 class Tree: Identifiable {
    let id: Int
    let title: String
    let items: [Tree]
    
    init(id: Int, title: String, items: [Tree]) {
        self.id = id
        self.title = title
        self.items = items
    }
 }

ContentView.swift:

 struct ContentView: View {
    let tree: [Tree]
    
    var body: some View {
        VStack {
            ForEach(tree, id: \.id) { treeItem in
                TreeViewItem(item: treeItem) {
                    VStack {
                        Text("-")
                    }
                }
            }
        }
    }
 }

 struct TreeViewItem<Content: View>: View {
    let item: Tree
    let content: Content

    init(item: Tree, @ViewBuilder content: () -> Content) {
        self.item = item
        self.content = content()
    }

    var body: some View {
        VStack {
            Text(item.title)
            ForEach(item.items, id: \.id) { treeItem in
                TreeViewItem(item: treeItem) {
                    VStack {
                        Text("-")
                    } as! Content
                }
            }
        }
        content
    }
 }

Output:

enter image description here

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.