Home > Back-end >  SwitUI: How to fetch data to tree view
SwitUI: How to fetch data to tree view

Time:10-13

I am making app that uses tree structure. I want to fetch data from json that has nested data in it, into the tree structure. I followed tutorial for that and I know how to achieve that statically but I want it to create itself after fetching data. This is custom tree structure:

struct Tree<A> {
    var value: A
    var children: [Tree<A>] = []
    init(_ value: A, children: [Tree<A>] = []) {
        self.value = value
        self.children = children
    }
}

extension Tree {
    func map<B>(_ transform: (A) -> B) -> Tree<B> {
        return Tree<B>(transform(value), children: children.map({ $0.map(transform) }))
    }
}

class Unique<A>: Identifiable {
    let value: A
    init(_ value: A) { self.value = value }
}

struct CollectDict<Key: Hashable, Value>: PreferenceKey {
    static var defaultValue: [Key:Value] { [:] }
    static func reduce(value: inout [Key:Value], nextValue: () -> [Key:Value]) {
        value.merge(nextValue(), uniquingKeysWith: { $1 })
    }
}

extension CGPoint: VectorArithmetic {
    public static func -= (lhs: inout CGPoint, rhs: CGPoint) {
        lhs = lhs - rhs
    }
    
    public static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
        return CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y)
    }
    
    public static func  = (lhs: inout CGPoint, rhs: CGPoint) {
        lhs = lhs   rhs
    }
    
    public mutating func scale(by rhs: Double) {
        x *= CGFloat(rhs)
        y *= CGFloat(rhs)
    }
    
    public static func   (lhs: CGPoint, rhs: CGPoint) -> CGPoint {
        return CGPoint(x: lhs.x   rhs.x, y: lhs.y   rhs.y)
    }
    
    public var magnitudeSquared: Double { return Double(x*x   y*y) }
}

struct Line: Shape {
    var from: CGPoint
    var to: CGPoint
    var animatableData: AnimatablePair<CGPoint, CGPoint> {
        get { AnimatablePair(from, to) }
        set {
            from = newValue.first
            to = newValue.second
        }
    }
    
    func path(in rect: CGRect) -> Path {
        Path { p in
            p.move(to: self.from)
            p.addLine(to: self.to)
        }
    }
}

struct Diagram<A: Identifiable, V: View>: View {
    let tree: Tree<A>
    let node: (A) -> V
    
    typealias Key = CollectDict<A.ID, Anchor<CGPoint>>

    var body: some View {
        VStack(alignment: .center) {
            node(tree.value)
               .anchorPreference(key: Key.self, value: .center, transform: {
                   [self.tree.value.id: $0]
               })
            HStack(alignment: .bottom, spacing: 10) {
                ForEach(tree.children, id: \.value.id, content: { child in
                    Diagram(tree: child, node: self.node)
                })
            }
        }.backgroundPreferenceValue(Key.self, { (centers: [A.ID: Anchor<CGPoint>]) in
            GeometryReader { proxy in
                ForEach(self.tree.children, id: \.value.id, content: {
                 child in
                    Line(
                        from: proxy[centers[self.tree.value.id]!],
                        to: proxy[centers[child.value.id]!])
                    .stroke()
                })
            }
        })
    }
}

This is my tree view:

struct RoundedCircleStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .frame(width: 50, height: 50)
            .background(Circle().stroke())
            .background(Circle().fill(Color.white))
            .padding(10)
    }
}


struct BinaryTreeView: View {
    @State var tree = Tree<Friut>(Friut(header: "", info: "", children: [Friut]()), children: [Tree<Friut>]()).map(Unique.init)
    @State var friut = Friut(header: "", info: "", children: [Friut]())
    @State var pickedFriutID = ""
       var body: some View {
           VStack {
               Diagram(tree: tree, node: { value in
                   Text("\(value.value.header)")
                       .modifier(RoundedCircleStyle())
               })
           }.onAppear {
               loadJson(filename: "test")
               let binaryTree = Tree<Friut>(friut, children: [
                    Tree(friut.children.first!),
                    Tree(friut.children.last!)
               ])
               
               tree = binaryTree.map(Unique.init)
           }
       }
    func loadJson(filename fileName: String) {
        if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
            do {
                let data = try Data(contentsOf: url)
                let decoder = JSONDecoder()
                let jsonData = try decoder.decode(JSONData.self, from: data)
                friut = jsonData.Fruits
                pickedFriutID = friut.id
            } catch {
                print("error:\(error)")
            }
        }
    }
}

Fruit Struct:

struct JSONData: Decodable {
    let Fruits: Friut
}

struct Friut: Decodable, Identifiable {
    let id: String = UUID().uuidString
    var header: String
    var info: String
    var children: [Self]
}

JSON File:

{
    "Fruits":
    {
        "header": "Apple",
        "info": "Apples are common fruits in Europe",
        "children": [
            {
                "header": "Golden Delicous",
                "info": "The Golden Delicous is one of the most popular Apples in the United States",
                "children" : [
                    {
                        "header": "Golden Delicous Lidl", "info": "The Golden Delicous sold from Lidl is more sour than the average of its kind",
                        "children" : []},
                    {
                        "header": "Golden Delicous Aldi",
                        "info": "The Golden Delicous Aldi is a steal for only 0.20ct per Apple!", "children" : []
                        
                    }]
            },
            {

                "header": "Pink Lady",
                "info": "The Pink Lady is very sweet apple", "children":[
                    {
                        "header": "Pink Lady America",
                        "info": "The American Pink Lady is much more red but less aromatic than other Pink Lady Apples",
                        "children": [
                            {
                                "header": "Pink Lady America 1",
                                "info": "Digusting 1", "children" : [
                                    {
                                        "header": "Pink Lady America 1.1",
                                        "info": "Disgusting 1.1", "children" : []
                                        
                                    },
                                    {
                                        "header": "Pink Lady America 1.2",
                                        "info": "Disgusting 1.2", "children": [
                                            {
                                                "header" : "Pink Lady America 1.2.1",
                                                "info": "Disgusting 1.2.1",
                                                "children": [
                                                    {
                                                        "header": "Pink Lady America 1.2.1.1",
                                                        "info":"Disgusting 1.2.1.1",
                                                        "children": []},
                                                {
                                                    "header": "Pink Lady America 1.2.1.2",
                                                    "info":"Disgusting 1.2.1.2",
                                                    "children": []
                                                }]
                                            }]
                                    }]
                                
                            },
                            {
                                "header": "Pink Lady America 2",
                                "info": "Digusting 2", "children" :[]
                            }]
                    },
                {
                    "header": "Pink Lady Europe",
                    "info" : "The European Pink Lady is much more aromatic than the American one but does not look as appetizing",
                    "children" : []
                }]
                
            }
        ]
        
    }
    
}

CodePudding user response:

When you are dealing with trees you should consider recursion. Try this example code, that creates the Tree with its children through a recursive function:

struct BinaryTreeView: View {
    @State var tree = Tree<Friut>(Friut(header: "", info: "", children: [Friut]()), children: [Tree<Friut>]()).map(Unique.init)
    @State var friut = Friut(header: "", info: "", children: [Friut]())
    @State var pickedFriutID = ""
    
    var body: some View {
        VStack {
            Diagram(tree: tree, node: { value in
                Text("\(value.value.header)").modifier(RoundedCircleStyle())
            })
        }.onAppear {
            loadJson(filename: "test")
            let binaryTree = asNodeTree(friut)  // <-- here
            tree = binaryTree.map(Unique.init)
        }
    }
    
    // -- here
    func asNodeTree(_ friut: Friut) -> Tree<Friut> {
         Tree(friut, children: friut.children.map{ asNodeTree($0) } )
    }
    
    func loadJson(filename fileName: String) {
        if let url = Bundle.main.url(forResource: fileName, withExtension: "json") {
            do {
                let data = try Data(contentsOf: url)
                let decoder = JSONDecoder()
                let jsonData = try decoder.decode(JSONData.self, from: data)
                friut = jsonData.Fruits
                pickedFriutID = friut.id
            } catch {
                print("error:\(error)")
            }
        }
    }
}

Note: you will have to increase the frame size of your RoundedCircleStyle to fit the text.

  • Related