SwitUI:如何将数据提取到树视图



我正在制作一个使用树结构的应用程序。我想从嵌套了数据的json中获取数据,并将其放入树结构中。我遵循了教程,我知道如何静态地实现这一点,但我希望它在获取数据后创建自己。这是自定义树结构:

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()
})
}
})
}
}

这是我的树视图:

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)")
}
}
}
}

水果结构:

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文件:

{
"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" : []
}]

}
]

}

}

在处理树时,应该考虑递归。试试这个示例代码,它通过递归函数创建Tree及其子级

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)")
}
}
}
}

注意:您必须增加RoundedCircleStyle的边框大小以适应文本。

最新更新