SwiftUI之View Tree 实战2(绘制二叉树)
2020/7/15 23:10:16
本文主要是介绍SwiftUI之View Tree 实战2(绘制二叉树),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本篇为大家带来SwiftUI中Preference的第二个实战教程,最后的实效效果如下图:
其实,用SwiftUI实现上图的二叉树还算简单,节点与节点之间的连线,需要用到Preference的知识。
定义数据结构
二叉树上的节点由两部分组成:
- 值
- 子节点
struct Tree<A>: Identifiable { let id = UUID().uuidString var value: A var children: [Tree<A>] = [] init(_ value: A, children: [Tree<A>] = []) { self.value = value self.children = children } } 复制代码
我们用value表示该节点的值,它是一个范型,因此该值可以显示任何类型的数据,用children表示该节点的子节点,之所以让Tree实现Identifiable,目的是后续的代码需要遍历子节点,会用到ForEach。
绘制节点
由于每个节点可能存在n多个子节点,并且子节点在父节点的下方,我们很自然的考虑使用VStack包装一个HStack,示意图如下:
struct DiagramSample<A, V: View>: View { let tree: Tree<A> let node: (A) -> V var body: some View { VStack(spacing: 10) { node(tree.value) HStack(alignment: .bottom, spacing: 10) { ForEach(tree.children) { child in DiagramSample(tree: child, node: self.node) } } } } } 复制代码
上边的代码很好理解,采用递归的方法显示各个节点。为了查看效果,我们举个🌰,我们先把节点中value的类型设为Int:Tree:
struct DiagramSampleExample: View { let binarytree = Tree<Int>(10, children: [ Tree<Int>(20, children: [ Tree(21), Tree(22) ]), Tree<Int>(30, children: [ Tree(31), Tree(32) ]) ]) var body: some View { DiagramSample(tree: binarytree, node: { value in Text("\(value)") .modifier(RoundedCircleStyle()) }) } } 复制代码
我们再把value的类型设置String:Tree(String):
struct DiagramSampleExample1: View { let binarytree = Tree<String>("爷爷", children: [ Tree<String>("大爷", children: [ Tree("大侄子"), Tree("二侄子") ]), Tree<String>("爸爸", children: [ Tree("大儿子"), Tree("小儿子") ]) ]) var body: some View { DiagramSample(tree: binarytree, node: { value in Text("\(value)") .modifier(RoundedCircleStyle()) }) } } 复制代码
完整实现
上边的内容只是一个小小的演示,接下来,我们讲解一下绘制二叉树的具体步骤,大家发现没有,绘制节点并没有用到Preference相关的知识,只有绘制节点与节点之间的连线的时候,才用到了这个技术。
基本思路如下:
- 我们需要知道每个节点的位置信息
- 把每个字节点的center同父节点连线
在本例中,我们使用AnchorPreference来获取节点的center,因此我们先定义一个PreferenceKey:
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 }) } } 复制代码
接下来是绘制图形,代码如下:
struct Diagram<A, V: View>: View { let tree: Tree<A> let node: (A) -> V typealias Key = CollectDict<String, Anchor<CGPoint>> var body: some View { VStack(spacing: 10) { node(tree.value) .anchorPreference(key: Key.self, value: .center, transform: { [self.tree.id: $0] }) HStack(alignment: .bottom, spacing: 10) { ForEach(tree.children) { child in Diagram(tree: child, node: self.node) } } } .backgroundPreferenceValue(Key.self) { (centers: [String: Anchor<CGPoint>]) in GeometryReader { proxy in ForEach(self.tree.children) { child in Line(from: proxy[centers[self.tree.id]!], to: proxy[centers[child.id]!]) .stroke() } } } } } 复制代码
我们通过.anchorPreference
为节点绑定信息,这个信息是一个字典,key为tree的id,value为center。在.backgroundPreferenceValue
中为节点连线。我们在看看Line这个结构体的定义:
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 { var path = Path() path.move(to: from) path.addLine(to: to) return path } } 复制代码
代码非常简单,就是绘制两点之间的path,我们实现了animatableData,目的是在插入数据的时候,Line的过渡效果是圆滑的,而不是瞬间改变。由于from和to的类型是CGPoint,它并没有实现VectorArithmetic协议,因此,我们需要手动实现该协议:
extension CGPoint: VectorArithmetic { public static func - (lhs: CGPoint, rhs: CGPoint) -> CGPoint { CGPoint(x: lhs.x - rhs.x, y: lhs.y - rhs.y) } public mutating func scale(by rhs: Double) { x *= CGFloat(rhs) y *= CGFloat(rhs) } public var magnitudeSquared: Double { 0 } public static func + (lhs: CGPoint, rhs: CGPoint) -> CGPoint { CGPoint(x: lhs.x + rhs.x, y: lhs.y + rhs.y) } } 复制代码
完成了上边的代码后,主要功能已经实现了,接下来,我们把这些代码组合起来:
struct DiagramExample: View { @State private var binarytree = Tree<Int>(130, children: [ Tree<Int>(20, children: [ Tree(21), Tree(22) ]), Tree<Int>(30, children: [ Tree(31), Tree(32) ]) ]) var body: some View { VStack { Diagram(tree: binarytree, node: { value in Text("\(value)") .modifier(RoundedCircleStyle()) }) Button("随机插入") { withAnimation { self.binarytree.insert(Int.random(in: 0...100)) } } } } } 复制代码
当点击随机插入的按钮后,我们为Tree随机插入一个0~100的整数,我们打算把这个二叉树做成二叉查找树,当前根节点的左边全部比根节点小,当前根节点的右边全部比根节点大。
extension Tree where A == Int { mutating func insert(_ number: Int) { if number < value { if children.count > 0 { children[0].insert(number) } else { children.append(Tree(number)) } } else { if children.count == 2 { children[1].insert(number) } else if children.count == 1, children[0].value > number { children[0].insert(number) } else { children.append(Tree(number)) } } } } 复制代码
总结
Preference的用处还有很多,在接下来的一个实战中,使用该技术可以为ScrollView添加下拉刷新功能,敬请期待。
参考:www.objc.io/blog/2019/1…
完整代码:SwiftUI-Tree.swift
SwiftUI集合:FuckingSwiftUI
这篇关于SwiftUI之View Tree 实战2(绘制二叉树)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2022-10-05Swift语法学习--基于协议进行网络请求
- 2022-08-17Apple开发_Swift语言地标注释
- 2022-07-24Swift 初见
- 2022-05-22SwiftUI App 支持多语种 All In One
- 2022-05-10SwiftUI 组件参数简写 All In One
- 2022-04-14SwiftUI 学习笔记
- 2022-02-23Swift 文件夹和文件操作
- 2022-02-17Swift中使用KVO
- 2022-02-08Swift 汇编 String array
- 2022-01-30SwiftUI3.0页面反向传值