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.