CoreText實現圖文混排之點選事件

發表於2016-05-26
CoreText實現圖文混排之點選事件

系列文章:


今天呢,我們繼續把CoreText圖文混排的點選事件補充上,這樣我們的圖文混排也算是圓滿了。

哦,上一篇的連結在這裡
CoreText實現圖文混排。所有需要用到的準備知識都在上一篇,沒有趕上車的朋友可以去補個票~

上正文。


CoreText做圖文混排之點選事件

主要思路

我們知道,CoreText是基於UIView去繪製的,那麼既然有UIView,就有

-(void)touchesBegan:(NSSet)touches withEvent:(UIEvent )event方法,我們呢,就是基於這個方法去做點選事件的。

上面呢就是主要思路。接下來呢,我們來詳細講解一下。還是老規矩,先上程式碼。

看上去也挺多的,我們還是分段講解吧。


分段解析

-touchesBegan

之所以把他放在首位,是因為他作為整個view響應點選事件的入口扮演者十分重要的角色。

他負責接收點選事件,根據條件將點選事件分發給不同的物件去執行相應的響應。

這裡老司機還是要解釋一下,為什麼我要設定成優先響應圖片的事件呢?

是這樣的,在我們使用的過程中,大部分的場景是如下過程:

  • 給整段富文字新增屬性,事件等
  • 插入圖片
  • 給圖片設定點選事件

正是因為這樣,我們可以看出邏輯上圖片的響應事件的優先順序明顯是要高於文字的。即使是一段文字範圍我們賦值了文字的響應事件,然後在範圍中插入了圖片並且賦予了圖片響應事件,我們往往是希望圖片響應其自己的事件。同時,不知道你們是否還記得上一趟車我們已經求出了圖片的frame,如果優先判斷出點選的是圖片的話將會減少很多計算量提高執行效率。所以我這裡將圖片的響應優先順序定義的高於文字,不過根據需要我們可以定義不同的響應優先順序。

搞明白這一點以後,其實邏輯就很簡單了。

  • 首先呢,先取出當前點選的到螢幕座標的點。
  • 將螢幕座標轉換為系統座標(不懂得同學快去上一節補課)
  • 判斷是否點選在圖片上
  • 如果未點選圖片執行點選文字

獲取點選座標

-touchesBegan事件給我們提供了touches這麼一個集合。裡面裝滿了UITouch物件。

因為集合是無序的,所以我們通過anyObject取出其中的一個UITouch物件。
UITouch物件的locationInView是專門用來給出UITouch物件在某個View中的座標的方法,因此我們可以用這個方法來求出當前點選位置的系統座標。這段比較基礎,想畫個重點都不知道畫哪。


座標轉換

這裡用到了第一個工具方法(老司機習慣把寫好的方法分類,這些中間方法老司機習慣叫他們工具方法),-(CGPoint)systemPointFromScreenPoint:(CGPoint)origin。

簡單的說一句,因為螢幕座標與系統座標的不同,我們要將座標系統一成系統座標,這樣才能計算,所以才有了這個座標轉換的方法。其實很簡單

上一講有座標系的圖,這裡我就不細講了。直接進入下一話題。


點選圖片判斷

第二個工具方法

-(BOOL)checkIsClickOnImgWithPoint:(CGPoint)location

這裡呢,我們用到了第三個工具方法,順便就說了吧

-(BOOL)isFrame:(CGRect)frame containsPoint:(CGPoint)point

事實上也是呼叫了系統的一個方法CGRectContainsPoint()。這個方法兩個引數,一個是frame,一個是point。可以返回point是否在frame中。

不過還是有一點需要注意的。由於傳入的point是系統座標(本例中),所以frame我們一定要傳入系統座標系下的frame才能正確對應。

這裡老司機偷了個懶,直接把上一講中求得的圖片frame改成了一個例項變數,這樣在這裡的方法中我就能直接呼叫了。這只是個demo,所以我就怎麼方便怎麼來了,實際使用中,你可以把frame儲存在陣列或字典中。你問我怎麼在陣列或字典中儲存一個frame這樣的結構體?恩,有一個系統類叫NSValue,專門針對這種結構體。

如果-(BOOL)isFrame:(CGRect)frame containsPoint:(CGPoint)point返回YES則說明在圖片範圍內,則響應圖片的點選事件

並且-(BOOL)checkIsClickOnImgWithPoint:(CGPoint)location返回YES,否則返回NO。

回到上一層,如果-(BOOL)checkIsClickOnImgWithPoint:(CGPoint)location返回YES,則說明點選的是圖片並且已經執行完響應事件直接return結束方法即可。否則則繼續檢查是否點選到了文字。


點選文字判斷

終於進入重中之重了,點選文字的邏輯了,不過你也別害怕,如果你對上一講的講解有了一定的理解的話,這裡將變得簡單一些。

CoreText實現圖文混排之點選事件
邏輯圖

看上去很多是吧?有沒有怕怕的。

仔細看你會發現,有很多程式碼跟昨天的有相似之處,就是這樣,因為這裡也遍歷了每一個CTRun,只不過更加細化到CTRun中的每個字

這四句我就不多說了,獲取所有CTLine和其原點。

獲取每個CTLine中包含的富文字在整串富文字中的範圍。將所有CTLine中字串的範圍儲存下來放入陣列備用。

這個for迴圈用來遍歷富文字中的每一個字元。下面的程式碼都是在for迴圈中的迴圈體。

這裡又是一層迴圈,通過當前字元序號i與每個CTLine包含字元的範圍比較來求得當前計算的是哪個CTLine中的字元

取得當前字元所在的CTLine並取得該CTLine的原點,同時通過這裡的第五個工具方法

-(CGRect)frameForCTRunWithIndex:(NSInteger)index
CTLine:(CTLineRef)line
origin:(CGPoint)origin

計算當前字元的frame。
分解講一下這個方法

根據註釋就能很輕易的看懂這段程式碼,不過可能有幾個方法不熟悉,我來介紹下。

  • CTLineGetOffsetForStringIndex(,,)

獲取一行文字中,指定charIndex字元相對x原點的偏移量,返回值與第三個引數同為一個值。如果charIndex超出一行的字元長度則反回最大長度結束位置的偏移量,如一行文字共有17個字元,哪麼返回的是第18個字元的起始偏移,即第17個偏移+第17個字元佔有的寬度=第18個起始位置的偏移。因此想求一行字元所佔的畫素長度時,就可以使用此函式,將charIndex設定為大於字元長度即可。

因為求得的座標是相對於CTLine原點的偏移量,因此我們要加上CTLine原點的x座標獲得該點的絕對座標

  • CTLineGetGlyphRuns()昨天有介紹過,拿到CTLine中的所有CTRun。
  • CTRunGetStringRange()獲得CTRun在富文字中的範圍
  • CTRunGetTypographicBounds(,,,,)獲得對應CTRun的尺寸資訊

中間用了第六個工具方法

-(BOOL)isIndex:(NSInteger)index inRange:(NSRange)range

這個程式碼很簡單我就不多說了。

通過以上方法,你就拿到了每一個字元的frame了。

可以返回至上一層了=。=喘了一口氣。。。

接受到字元的frame,還是判斷點選位置是否在frame中,如果在,則響應點選事件並結束方法。如果沒有不在任何一個字元的frame內,則說明沒有點選到文字,執行相應的點選事件。

大工告成,到了這裡,CoreText做圖文混排的點選事件也算是完成了。

最後放一張效果圖吧。

CoreText實現圖文混排之點選事件

吶,了卻一樁心事。。。

你要是喜歡呢,麻煩你動一動你可愛的小手點選一下喜歡或者關注,畢竟老司機這麼愛慕虛榮的人,而且老司機會經常更新的。

哦,這段程式碼是我自己的解決方案,所以要轉載的同學,一定要註明出處哦,這次是一定哦。貌似你不註明我也攔不住你。。。嘖嘖嘖。。。
http://www.jianshu.com/p/51c47329203e

參考資料:

2016年05月16日23點52分

老司機Wicky

相關文章