1.首先找到AppDelegate類,無論一個工程有多麼復雜,多麼多的類,但入口只有一個就是AppDelegate類的didFinishLaunchingWithOptions方法。我們閱讀別人源代碼的時候可以從這裡入手。在TQRichTextViewDemo工程中,這個方法中僅創建了一個TQViewController並設置為window的rootViewController。
2.去看TQViewController的實現。首先也是從初始化方法開始,沒有initWithNib,那麼就看viewDidLoad。在這個方法中,只創建了一個TQRichTextView,設置了frame,text,backgroundColor,delegate等屬性,然後添加到了self.view上。看完viewDidLoad方法,發現這是系統調用的最後一個方法,那麼結合模擬器的運行效果,我們可以猜測,文字的解析和繪制過程被封裝到了TQRichTextView中。
3.去看TQRichTextView。
- 浏覽一下頭文件,了解有哪幾個屬性和可調用的方法。
- 閱讀initWithFrame方法,發現這裡只是對幾個屬性的初始化。
- 尋找是否有重寫的set,get方法。發現setText,setFont,setTextColor,setLineSpacing方法。閱讀發現,每次更新完值後會調用setNeedsDisplay方法。說明,每次對這四個對象賦值後都會調用drawRect。
- 繼續閱讀.m文件,發現drawRect方法,這個也是有系統調用的,那麼繪制的工作應該是在這裡完成。閱讀drawRect方法。
- 在drawRect中首先調用了analyzeText方法,傳入了參數_text,並把返回值賦給了_textAnalyzed。
- 去看analyzeText方法。首先清空了數組和字典,然後定義了一個NSString類型的result變量和NSMutableArray類型的array並賦值為richTextRunsArray。
- 執行TQRichTextEmojiRun的一個方法,傳入string和array的指針。
- 因為這個方法是TQRichTextEmojiRun的類方法,所有我們直接去看該方法,而不用去看TQRichTextEmojiRun的初始化方法。連蒙帶猜的我們可以確定這個方法應該是解析字符串中表情的方法。看重點,發現如果解析到表情,那麼就創建一個TQRichTextEmojiRun的隊形,range=表情字符串在string中的range,originalText=表情字符串,把這個創建的對象放入傳進來的數組指針中,最後替換傳入字符串中的表情字符串為空格,並返回被替換過表情的字符串。
- 通過看TQRichTextEmojiRun的init方法,發現初始化時設置了兩個變量,來自父類TQRichTextBaseRun,分別設置了TQRichTextRunType和是否響應觸摸。發現TQRichTextURLRun也繼承自TQRichTextBaseRun。
- 跳出這個方法,回到analyzeText中,繼續往下看。
- 執行TQRichTextURLRun的一個方法,傳入result和array的指針。
- 同上,直接去看這個方法的實現。從和TQRichTextEmojiRun的命名格式類似,調用的方法名類似,我們可以猜測這個方法是解析URL的,那麼就直接看重點。發現,如果找到匹配的字符串,就創建一個TQRichTextURLRun的對象,設置range=URL的range,originText = URL,然後把對象添加到傳進來的數組中,最後返回string。
- 通過看TQRichTextURLRun的init方法,發現同上初始化時設置了兩個變量,來自父類TQRichTextBaseRun,分別設置了TQRichTextRunType和是否響應觸摸。發現TQRichTextEmojiRun繼承自TQRichTextImageRun,TQRichTextImageRun繼承自TQRichTextBaseRun。
- 跳出這個方法,回到analyzeText中,繼續往下看。
- 遍歷richTextRunsArray中的對象,每個對象調用setOriginalFont方法,傳入參數self.font。通過閱讀上面的兩個方法的內部實現明白richTextRunsArray中保存的是TQRichTextEmojiRun和TQRichTextURLRun對象,所以去看這兩個對象的setOriginalFont方法。
- 這個設置的是繼承自父類TQRichTextBaseRun的屬性。
- 回到analyzeText繼續往下看。
- 返回result,跳出這個方法,繼續看drawRect。
- 接下來是創建NSAttrbutedString,並賦值一些屬性。
- 文本處理,遍歷richTextRunsArray中的對象,每個對象調用replaceTextWithAttributedString方法。
- 在TQRichTextEmojiRun沒有找到replaceTextWithAttributedString方法,那就去TQRichTextImageRun中找,閱讀該方法。
- 傳入的attrString刪除占位的空格字符。
- 創建CTRunDelegateCallbacks,返現設定的寬高為OriginalFont的高度的1.1倍。
- 創建空格NSAttrbutedString對象,添加CTRunDelegateRef屬性。
- 把空格NSAttrbutedString對象插入至傳入的attrString。
- 查看super方法,發現在這段range上還設置了一個鍵值對,key為TQRichTextAttribute,value為自己。
- 同上閱讀TQRichTextURLRun的replaceTextWithAttributedString方法。
- 為該段range的文字添加藍色字體。
- 調用super方法,為該段range添加同上的key-value鍵值對。
- 返回繼續閱讀drawRect。
- 下面是繪制的准備工作,不想研究咋繪制的粗讀就行。
- 清空richTextRunRectDic
- 繪制,不想研究的也可粗讀略過。
- 找重點,繪制替換過的特殊文本單元。
- 遍歷取出每一個run,取出run的attributes中key為TQRichTextAttribute的值。
- 如果值存在,則說明,這個run是特殊的run。
- 求run的frame,粗讀略過。
- 調用drawRunWithRect方法,並將返回值賦給idDraw變量。
- TQRichTextEmojiRun的drawRunWithRect方法中繪制了表情圖片,返回YES。
- TQRichTextURLRun的drawRunWithRect方法返回NO。
- TQRichTextBaseRun的drawRunWithRect方法返回NO。
- 判斷run的isResponseTouch屬性,查找後發現是TQRichTextBaseRun的屬性,注釋說是是否響應觸摸
- 如果響應觸摸
- 在richTextRunRectDic中添加鍵值對,key為rect,value為run。
- 設置循環的條件,釋放CF類型的變量。CF類型的變量不支持ARC。
- 繼續尋找TQRichTextView中重載系統的方法。返現touchesBegan和touchesEnd方法。閱讀這兩個方法。
- 拿到觸摸的點,轉換點的坐標為以左下角為原點是的坐標。
- 判斷是否能只想delegate
- 遍歷richTextRunRectDic,判斷點是否在rect中。如果在則調用delegate方法。
- 主要功能分析完畢。
- 通讀TQRichTextBaseRun,了解其它屬性和一些屬性的默認值。
- 至此,整個TQRichTextViewDemo基本分析完畢。可得出如下結論。
- 類的結構關系
- TQRichTextEmojiRun—> TQRichTextImageRun—> TQRichTextBaseRun。
- TQRichTextURLRun—> TQRichTextBaseRun。
- TQRichTextEmojiRun和TQRichTextURLRun分別實現了analyzeText:runsArray方法用來從字符串中取出所需要的文字,並創建TQRichTextBaseRun對象添加進數組保存。
- TQRichTextEmojiRun和TQRichTextURLRun分別重載了replaceTextWithAttributedString方法來在字符串的特定range處添加Attributed屬性。
- TQRichTextEmojiRun和TQRichTextURLRun分別重載了drawRunWithRect方法實現了自定義位置,並返回是否繪制了內容。
- 流程
- 給TQRichTextView更改屬性。
- 調用drawRect。
- 調用TQRichTextEmojiRun和TQRichTextURLRun的analyzeText:runsArray方法解析字符串。
- 創建NSAttributedString。
- 為NSAttributedString添加屬性。
- 調用TQRichTextEmojiRun和TQRichTextURLRun的replaceTextWithAttributedString方法為字符串添加屬性。
- CoreText繪制NSAttributedString。
- 調用TQRichTextEmojiRun和TQRichTextURLRun的drawRunWithRect方法實現自定義繪制。
- 保存run和rect進字典,從字典取值判斷是否能點擊。
- 有以上可以得出結論,若要更換表情和文字的解析規則,則只需去TQRichTextEmojiRun或TQRichTextURLRun修改analyzeText:runsArray,replaceTextWithAttributedString,drawRunWithRect三個方法。
- analyzeText:runsArray修改解析規則。
- replaceTextWithAttributedString修改添加屬性。
- drawRunWithRect修改自定義繪制。
- 若要實現點擊,只需設置isResponseTouch為YES並實現TQRichTextViewDelegate。
- 由以上閱讀代碼發現如下缺陷
- TQRichTextView的awakeFromNib方法沒實現,如過使用xib拖拽,則無法為TQRichTextView添加默認屬性。
- 解決方法,添加awakeFromNib方法,並在其中為默認屬性賦值。
- sizeWithFont和boundingRectWithSize:options:attributes:context:方法計算時不會計算行間距,TQRichTextView的lineSpacing若不設置為0會導致兩方法計算的高度比TQRichTextView實際顯示需要的高度小。
- 解決方法,TQRichTextView的lineSpacing設置為0。