上篇博客《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