上篇博客《iOS可视化动态绘制八种排序过程》可视化了一下一些排序的过程,本篇博客就来聊聊图的东西。在以前的博客中详细的讲过图的相关内容,好比《图的物理存储结构与深搜、广搜》。固然以前写的程序是比较抽象的。上篇博客咱们以可视化的方式看了一下各类排序的过程,今天博客中咱们就来可视化的看一下图的相关部分,今天咱们要画的图是无向图,而且每一个点到其余点都有直接的连线。今天咱们就基于此图来作一些事情。固然本篇博客在画图时咱们使用的是Bezier曲线来画的,由于以前也聊过关于Bezier的相关东西,因此今天就不对Bezier作过多赘述了。html
今天的博客咱们有易到难大体分为三个部分。第一部分咱们会画出相应的图,并该图是能够对每一个点进行拖动的,在拖动的过程当中,咱们对其进行重绘。第二部分会取消拖动,使用UIView自带的动画来让其本身变换,固然本部分你也可使用Timer或者GCD的TimerSource让其运动。第三部分则是第二部分的升级,再第二部分的基础上咱们稍做改进,此部分咱们使用的是DispatchSourceTimer来让每一个点进行运动的。在第三部分咱们让局部范围的点进行连线,也就是在运动的过程当中,咱们须要找出在当前点的规定范围内有哪些点,而后将这些点进行链接。git
上述这三部分的内容下方会详细的进行介绍,并会附有相应的运行结果图。接下来就进入咱们的主题部分。github
1、图的绘制数据结构
在本篇博客的第一部分咱们要按照要求先把图给绘制出来,咱们会随机的生成几个坐标点,而后在这些坐标点上添加上View,而后再将这些坐标点使用Bezier进行链接。固然,在链接时咱们使用的是邻接矩阵来记录的每两点之间的关系。在绘制的过程当中,咱们会随机的为每一个点每条边分配颜色。闭包
当相应的图绘制好后,咱们须要为每一个点添加上Move事件,在对每一个点进行拖动时,咱们会及时的从新绘制整个图的关系。下方就是咱们本部分要实现内容的运行效果,以下所示:dom
若是理解了数据结构中图的构建,实现上述效果,并不困难。解析来咱们就来看一下实现上述效果的核心代码。函数
一、图的节点View的封装post
首先咱们来封装上述图的节点View,固然此节点View的封装比较简单。核心就在于给每一个节点View添加一个TouchesMoved事件,而后在TouchesMoved事件执行时,将触摸的移动点设置成当前View的Center便可。这样咱们就能够拖动每一个节点View了。在拖动节点View时,咱们还须要将拖动的事件回调到节点View的父视图上,让父视图知道当前用户拖动的是哪一个View。接下来咱们就来看一下节点View的核心代码。动画
下方这段代码的上一部分就是咱们定义的一个闭包类型,用来将节点View的触摸事件回调给父视图。该闭包类型须要传一个参数,该参数就是当前View的Tag, 这样父视图就知道当前用户拖动的是哪一个节点了。url
而randomColor()函数则是用来负责随机生成颜色的,上面每次颜色的变化都是使用的下方这个函数所随机生成的UIColor对象。
下方这段就是节点View的TouchesMoved事件,在该事件中咱们获取到当前用户触摸移动的坐标点,而后将该点赋值给当前节点View的Center,而后调用更新父视图的闭包回调对象便可。以下所示:
二、图View的封装
接下来咱们要实现画图的View了,也就是上述节点View的父视图了。父视图主要负责的工做内容就是建立上述的节点View,而后使用Bezier将每一个节点进行链接便可。固然,在用户拖动相应的View的时候,须要对当前图进行重绘。
下方这个方法就是往父视图上添加相应的节点视图,在节点视图初始化后,要设置一个闭包回调,该回调用来移动后图的重绘。在该闭包回调中,咱们会调用drawLine()方法。固然在建立节点View时,咱们也建立了相应的BezierPath的对象。每一个节点对应一个BezierPath对象,用来绘制该节点所连节点的线。具体代码以下所示:
咱们整个图的关系是存储在邻接矩阵中的,因此咱们要对邻接矩阵进行建立,在重绘时要对该邻接矩阵进行初始化。下方就是该邻接矩阵建立和初始化的代码,关于邻接矩阵的内容在此就不作过多赘述了,具体内容请参考以前的博客。
节点View和邻接矩阵的准备工做完成后,接下来就是画线的工做了。下方就是画线的核心代码,在画线以前咱们要先将相应的BezierPath对象上的点移除掉,而后再添加上新的点,最后就是进行重绘了。在往BezierPath对象上添加点时,咱们要将节点的关系在邻接矩阵中进行记录。若是两个点之间已经画完线了,那么邻接矩阵上的内容咱们设置为true,未画线的节点之间则是false。具体代码以下所示。
在上述方法调用setNeedsDisplay()方法后,就会执行View的draw()方法,咱们就在此方法中进行线条的绘制。固然下方的代码比较简单,在此就不作过多赘述了。
上述这些代码就是本部分所展现的效果图核心代码,完整示例请移步本篇博客末尾的github分享连接。
2、图的自动变换
上一部分是咱们手动的拖动让建立的图进行变换的,接下来咱们对上述代码进行改造一下,使其自动的进行变换。在点自动移动时,若是碰到屏幕的边界,咱们让其反弹接着进行移动。下方就是咱们本部分要实现的效果。
固然有了第一部分做为基础,咱们实现本部分的效果并不复杂。咱们须要作的事情是随机生成每一个节点所移动的方向。而后判断移动时是否是超出屏幕范围,若是超出屏幕范围咱们就要对运动方向进行修正,让其往反方向进行移动。本部分咱们只须要修改节点View,而节点View的父视图不作修改。
下方这段代码片就是为了让其自动变换所实现的方法。下方的这两个方法会替换掉第一部分的TouchesMoved方法。下方的randomIncrement()方法用来生成当前View的x坐标和y坐标的偏移量。x的偏移量为1则表示往右运动,-1表示往左运动。y的偏移量为1则往下运动,-1则是往上运行。
下方的changePoint()就是根据x和y的偏移量不断修改当前节点View的坐标的方法。为了简单,此处使用了UIView自带的Animate来实现的。在修改x和y坐标的值时要判断是否超出屏幕边距,若是超出屏幕边界就往反方向移动。为了让点一直运动下去,咱们须要不断的调用changePoint()方法,以下所示。固然每调用一次changePoint()方法,咱们就须要调用一下重绘的回调。具体代码以下所示。
3、特定区域内画图
接下来咱们要作的就是继续在上述内容中作一些东西。在节点自动运动的过程当中,咱们不把全部的点都链接起来,本部分要作的事情是当点运动时,咱们以改点为中心划定个区域,若是有其余点在该区域内,咱们就将该区域内的点进行链接。若是点在运动的过程当中超出了划定的范围,那么咱们就去除以前画的线。效果以下所示:
本部分主要修改的内容是节点View的父视图,核心就是要计算当前点与周围点的距离,若是该距离小于咱们规定的距离的话,那么咱们就画线,不然就不画线。下方代码片断就是本部分的核心代码。主要就是往贝塞尔上添加点时进行距离的判断。下方的countDistance()函数就是用来计算两点之间直线距离的函数,在areaPoints()中调用了该函数来肯定当前区域中的点。核心代码以下所示:
4、点击新增节点
本部分也将在上述部分的代码上进行更新。该部分要作的事情是点击屏幕,往屏幕上添加新的节点。这一点在上述基础上实现是比较简单的。只需给节点的父View添加上新的节点便可。下方就是第四部分要实现的效果,每点击一次屏幕,就会在屏幕点击的地方生成一个节点,该节点就会运动。具体效果以下所示。
要想实现上述效果,下方是咱们修改的代码片断。就是给父视图添加了一个TouchesEnded事件,在点击的地方生成一个节点View便可。具体以下所示:
本篇博客Demo的github分享地址为:https://github.com/lizelu/FlyOver