上篇部落格《iOS可視化動态繪制八種排序過程》可視化了一下一些排序的過程,本篇部落格就來聊聊圖的東西。在之前的部落格中詳細的講過圖的相關内容,比如《圖的實體存儲結構與深搜、廣搜》。當然之前寫的程式是比較抽象的。上篇部落格我們以可視化的方式看了一下各種排序的過程,今天部落格中我們就來可視化的看一下圖的相關部分,今天我們要畫的圖是無向圖,并且每個點到其他點都有直接的連線。今天我們就基于此圖來做一些事情。當然本篇部落格在畫圖時我們使用的是Bezier曲線來畫的,因為之前也聊過關于Bezier的相關東西,是以今天就不對Bezier做過多贅述了。
今天的部落格我們有易到難大緻分為三個部分。第一部分我們會畫出相應的圖,并該圖是可以對每個點進行拖動的,在拖動的過程中,我們對其進行重繪。第二部分會取消拖動,使用UIView自帶的動畫來讓其自己變換,當然本部分你也可以使用Timer或者GCD的TimerSource讓其運動。第三部分則是第二部分的更新,再第二部分的基礎上我們稍作改進,此部分我們使用的是DispatchSourceTimer來讓每個點進行運動的。在第三部分我們讓局部範圍的點進行連線,也就是在運動的過程中,我們需要找出在目前點的規定範圍内有哪些點,然後将這些點進行連接配接。
上述這三部分的内容下方會詳細的進行介紹,并會附有相應的運作結果圖。接下來就進入我們的主題部分。
一、圖的繪制
在本篇部落格的第一部分我們要按照要求先把圖給繪制出來,我們會随機的生成幾個坐标點,然後在這些坐标點上添加上View,然後再将這些坐标點使用Bezier進行連接配接。當然,在連接配接時我們使用的是鄰接矩陣來記錄的每兩點之間的關系。在繪制的過程中,我們會随機的為每個點每條邊配置設定顔色。
當相應的圖繪制好後,我們需要為每個點添加上Move事件,在對每個點進行拖動時,我們會及時的重新繪制整個圖的關系。下方就是我們本部分要實作内容的運作效果,如下所示:

如果了解了資料結構中圖的建構,實作上述效果,并不困難。解析來我們就來看一下實作上述效果的核心代碼。
1、圖的節點View的封裝
首先我們來封裝上述圖的節點View,當然此節點View的封裝比較簡單。核心就在于給每個節點View添加一個TouchesMoved事件,然後在TouchesMoved事件執行時,将觸摸的移動點設定成目前View的Center即可。這樣我們就可以拖動每個節點View了。在拖動節點View時,我們還需要将拖動的事件回調到節點View的父視圖上,讓父視圖知道目前使用者拖動的是哪個View。接下來我們就來看一下節點View的核心代碼。
下方這段代碼的上一部分就是我們定義的一個閉包類型,用來将節點View的觸摸事件回調給父視圖。該閉包類型需要傳一個參數,該參數就是目前View的Tag, 這樣父視圖就知道目前使用者拖動的是哪個節點了。
而randomColor()函數則是用來負責随機生成顔色的,上面每次顔色的變化都是使用的下方這個函數所随機生成的UIColor對象。
下方這段就是節點View的TouchesMoved事件,在該事件中我們擷取到目前使用者觸摸移動的坐标點,然後将該點指派給目前節點View的Center,然後調用更新父視圖的閉包回調對象即可。如下所示:
2、圖View的封裝
接下來我們要實作畫圖的View了,也就是上述節點View的父視圖了。父視圖主要負責的工作内容就是建立上述的節點View,然後使用Bezier将每個節點進行連接配接即可。當然,在使用者拖動相應的View的時候,需要對目前圖進行重繪。
下方這個方法就是往父視圖上添加相應的節點視圖,在節點視圖初始化後,要設定一個閉包回調,該回調用來移動後圖的重繪。在該閉包回調中,我們會調用drawLine()方法。當然在建立節點View時,我們也建立了相應的BezierPath的對象。每個節點對應一個BezierPath對象,用來繪制該節點所連節點的線。具體代碼如下所示:
我們整個圖的關系是存儲在鄰接矩陣中的,是以我們要對鄰接矩陣進行建立,在重繪時要對該鄰接矩陣進行初始化。下方就是該鄰接矩陣建立和初始化的代碼,關于鄰接矩陣的内容在此就不做過多贅述了,具體内容請參考之前的部落格。
節點View和鄰接矩陣的準備工作完成後,接下來就是畫線的工作了。下方就是畫線的核心代碼,在畫線之前我們要先将相應的BezierPath對象上的點移除掉,然後再添加上新的點,最後就是進行重繪了。在往BezierPath對象上添加點時,我們要将節點的關系在鄰接矩陣中進行記錄。如果兩個點之間已經畫完線了,那麼鄰接矩陣上的内容我們設定為true,未畫線的節點之間則是false。具體代碼如下所示。
在上述方法調用setNeedsDisplay()方法後,就會執行View的draw()方法,我們就在此方法中進行線條的繪制。當然下方的代碼比較簡單,在此就不做過多贅述了。
上述這些代碼就是本部分所展示的效果圖核心代碼,完整示例請移步本篇部落格末尾的github分享連結。
二、圖的自動變換
上一部分是我們手動的拖動讓建立的圖進行變換的,接下來我們對上述代碼進行改造一下,使其自動的進行變換。在點自動移動時,如果碰到螢幕的邊界,我們讓其反彈接着進行移動。下方就是我們本部分要實作的效果。
當然有了第一部分作為基礎,我們實作本部分的效果并不複雜。我們需要做的事情是随機生成每個節點所移動的方向。然後判斷移動時是不是超出螢幕範圍,如果超出螢幕範圍我們就要對運動方向進行修正,讓其往反方向進行移動。本部分我們隻需要修改節點View,而節點View的父視圖不做修改。
下方這段代碼片就是為了讓其自動變換所實作的方法。下方的這兩個方法會替換掉第一部分的TouchesMoved方法。下方的randomIncrement()方法用來生成目前View的x坐标和y坐标的偏移量。x的偏移量為1則表示往右運動,-1表示往左運動。y的偏移量為1則往下運動,-1則是往上運作。
下方的changePoint()就是根據x和y的偏移量不斷修改目前節點View的坐标的方法。為了簡單,此處使用了UIView自帶的Animate來實作的。在修改x和y坐标的值時要判斷是否超出螢幕邊距,如果超出螢幕邊界就往反方向移動。為了讓點一直運動下去,我們需要不斷的調用changePoint()方法,如下所示。當然每調用一次changePoint()方法,我們就需要調用一下重繪的回調。具體代碼如下所示。
三、特定區域内畫圖
接下來我們要做的就是繼續在上述内容中做一些東西。在節點自動運動的過程中,我們不把所有的點都連接配接起來,本部分要做的事情是當點運動時,我們以改點為中心劃定個區域,如果有其他點在該區域内,我們就将該區域内的點進行連接配接。如果點在運動的過程中超出了劃定的範圍,那麼我們就去除之前畫的線。效果如下所示:
本部分主要修改的内容是節點View的父視圖,核心就是要計算目前點與周圍點的距離,如果該距離小于我們規定的距離的話,那麼我們就畫線,否則就不畫線。下方代碼片段就是本部分的核心代碼。主要就是往貝塞爾上添加點時進行距離的判斷。下方的countDistance()函數就是用來計算兩點之間直線距離的函數,在areaPoints()中調用了該函數來确定目前區域中的點。核心代碼如下所示:
四、點選新增節點
本部分也将在上述部分的代碼上進行更新。該部分要做的事情是點選螢幕,往螢幕上添加新的節點。這一點在上述基礎上實作是比較簡單的。隻需給節點的父View添加上新的節點即可。下方就是第四部分要實作的效果,每點選一次螢幕,就會在螢幕點選的地方生成一個節點,該節點就會運動。具體效果如下所示。
要想實作上述效果,下方是我們修改的代碼片段。就是給父視圖添加了一個TouchesEnded事件,在點選的地方生成一個節點View即可。具體如下所示:
本篇部落格Demo的github分享位址為:https://github.com/lizelu/FlyOver
作者:青玉伏案
出處:http://www.cnblogs.com/ludashi/
本文版權歸作者和共部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。
如果文中有什麼錯誤,歡迎指出。以免更多的人被誤導。
收履歷:某網際網路公司,招聘iOS/Android靠譜工程師,入職後,可内部聯系樓主,有小禮品贈送,有意者可郵箱投遞履歷:[email protected]