iOS使用UITableView實(shí)現(xiàn)的富文本編輯器

前言

公司最近做一個(gè)項(xiàng)目,其中有一個(gè)模塊是富文本編輯模塊,之前沒(méi)做個(gè)類似的功能模塊,本來(lái)以為這個(gè)功能很常見(jiàn)應(yīng)該會(huì)有已經(jīng)造好的輪子,或許我只要找到輪子,研究下輪子,然后修改打磨輪子,這件事就八九不離十了。不過(guò),還是?too young to simple了,有些事,還是得自己去面對(duì)的,或許這就叫做成長(zhǎng),感覺(jué)最近一年,對(duì)于編程這件事,更多了一點(diǎn)熱愛(ài),我感覺(jué)我不配過(guò)只會(huì)復(fù)制粘貼代碼的人生,編程需要有挑戰(zhàn)。所以,遇到困難,保持一份正念,路其實(shí)就在腳下,如果沒(méi)有困難,那就制造困哪,迎難而上,人生沒(méi)有白走的路,每一步都算數(shù),毒雞湯就到此為止,下面是干貨了。

結(jié)果

實(shí)現(xiàn)的功能包含了:

編輯器文字編輯

編輯器圖片編輯

編輯器圖文混排編輯

編輯器圖片上傳,帶有進(jìn)度和失敗提示,可以重新上傳操作

編輯器模型轉(zhuǎn)換為HTML格式內(nèi)容

簡(jiǎn)單的本地?cái)?shù)據(jù)存儲(chǔ)和恢復(fù)編輯實(shí)現(xiàn)(草稿箱功能)

配套的Java實(shí)現(xiàn)的服務(wù)器

后期有進(jìn)行了性能的優(yōu)化,可以看我的這篇文章:?iOS使用Instrument-Time Profiler工具分析和優(yōu)化性能問(wèn)題

以及客戶端代碼開源托管地址:MMRichTextEdit

還有java實(shí)現(xiàn)的文件服務(wù)器代碼開源托管地址:javawebserverdemo

沒(méi)圖沒(méi)真相,下面是幾張實(shí)現(xiàn)的效果圖




調(diào)研分析

基本上有以下幾種的實(shí)現(xiàn)方案:

UITextView結(jié)合NSAttributeString實(shí)現(xiàn)圖文混排編輯,這個(gè)方案可以在網(wǎng)上找到對(duì)應(yīng)的開源代碼,比如?SimpleWord?的實(shí)現(xiàn)就是使用這種方式,不過(guò)缺點(diǎn)是圖片不能有交互,比如說(shuō)在圖片上添加進(jìn)度條,添加上傳失敗提示,圖片點(diǎn)擊事件處理等等都不行,如果沒(méi)有這種需求那么可以選擇這種方案。

使用WebView通過(guò)js和原生的交互實(shí)現(xiàn),比如?WordPress-Editor、RichTextDemo?,主要的問(wèn)題就是性能不夠好,還有需要你懂得前端知識(shí)才能上手。

使用CoreText或者TextKit,這種也有實(shí)現(xiàn)方案的開源代碼,比如說(shuō)這個(gè)?YYText,這個(gè)很有名氣,不過(guò)他使用的圖片插入編輯圖片的位置是固定的,文字是圍繞著圖片,所以這種不符合我的要求,如果要使用這種方案,那修改的地方有很多,并且CoreText/TextKit使用是有一定的門檻的。

使用UITableView結(jié)合UITextView的假實(shí)現(xiàn),主要的思路是每個(gè)Cell是一個(gè)文字輸入的UITextView或者是用于顯示圖片使用的UITextView,圖片顯示之所以是選擇UITextView是因?yàn)閳D片位置需要有輸入光標(biāo),所以使用UITextView結(jié)合NSAttributeString的方式正好可以實(shí)現(xiàn)這個(gè)功能。圖片和文字混排也就是顯示圖片的Cell和顯示文字的Cell混排就可以實(shí)現(xiàn)了,主要的工作量是處理光標(biāo)位置輸入以及處理光標(biāo)位置刪除。

選型定型

前面三種方案都有了開源的實(shí)現(xiàn),不過(guò)都不滿足需要,只有第二種方案會(huì)比較接近一點(diǎn),不過(guò)WebView結(jié)合JS的操作確實(shí)是性能不夠好,內(nèi)存占用也比較高,?WordPress-Editor?、RichTextDemo?,這兩種方法實(shí)現(xiàn)的編輯器會(huì)明顯的感覺(jué)到不夠流暢,并且離需要還有挺大的距離,所有沒(méi)有選擇在這基礎(chǔ)上進(jìn)行二次開發(fā)。第三種方案在網(wǎng)上有比較多的人推薦,不過(guò)我想他們大概也只是推薦而已,真正實(shí)現(xiàn)起來(lái)需要花費(fèi)大把的時(shí)間,需要填的坑有很多,考慮到時(shí)間有限,以及項(xiàng)目的進(jìn)度安排,這個(gè)坑我就沒(méi)有去踩了。

我最終選擇的是第四種方案,這種方案好的地方就是UITableView、UITextView都是十分熟悉的組件,使用組合的模式通過(guò)以上的分析,理論上是沒(méi)有問(wèn)題的,并且,UITableView有復(fù)用Cell的優(yōu)勢(shì),所以時(shí)間性能和空間性能應(yīng)該是不差的。

實(shí)現(xiàn)細(xì)節(jié)分析

使用UITableView集合UITextView的這種方案有很多細(xì)節(jié)需要注意

Cell中添加UITextView,文字輸入換行或者超過(guò)一行Cell高度自動(dòng)伸縮處理

Cell中添加UITextView顯示圖片的處理

光標(biāo)處刪除和添加圖片的處理,換行的處理

需要解決問(wèn)題,好的是有些是已經(jīng)有人遇到并且解決的,其他的即使其他人沒(méi)有遇到過(guò),作為第一個(gè)吃螃蟹的人,我們?cè)敿?xì)的去分析下其實(shí)也不難

這個(gè)問(wèn)題剛好有人遇到過(guò),這里就直接發(fā)鏈接了iOS UITextView 輸入內(nèi)容實(shí)時(shí)更新cell的高度

實(shí)現(xiàn)上面效果的基本原理是:

1.在 cell 中設(shè)置好 text view 的 autolayout,讓 cell 可以根據(jù)內(nèi)容自適應(yīng)大小

2.text view 中輸入內(nèi)容,根據(jù)內(nèi)容更新 textView 的高度

3.調(diào)用 tableView 的 beginUpdates 和 endUpdates,重新計(jì)算 cell 的高度

4.將 text view 更新后的數(shù)據(jù)保存,以免 table view 滾動(dòng)超過(guò)一屏再滾回來(lái) text view 中的數(shù)據(jù)又不刷新成原來(lái)的數(shù)據(jù)了。

注意:上面文章中提到的思路是對(duì)的,不過(guò)在開發(fā)過(guò)程中遇到一個(gè)問(wèn)題:使用自動(dòng)布局計(jì)算高度的方式調(diào)用 tableView 的 beginUpdates 和 endUpdates,重新計(jì)算 cell 的高度會(huì)出現(xiàn)一個(gè)嚴(yán)重的BUG,textView中的文字會(huì)偏移導(dǎo)致不在正確的位置,所以實(shí)際的項(xiàng)目中禁用了tableView自動(dòng)計(jì)算Cell高度的特性,采用手動(dòng)計(jì)算Cell高度的方式,具體的可以看我的項(xiàng)目代碼。

2.這個(gè)問(wèn)題很簡(jiǎn)單,使用屬性文字就行了,下面直接貼代碼了

NSAttributedString結(jié)合NSTextAttachment就行了

3.這個(gè)問(wèn)題比較棘手,我自己也是先把可能的情況列出來(lái),然后一個(gè)一個(gè)分支去處理這些情況,不難就是麻煩,下面的文本是我寫在?備忘錄?上的情況分析,- [x] 這種標(biāo)識(shí)這種情況已經(jīng)實(shí)現(xiàn),- [ ] 這種標(biāo)識(shí)暫時(shí)未實(shí)現(xiàn),后面這部分會(huì)進(jìn)行優(yōu)化,主要的工作已經(jīng)完成了,優(yōu)化的工作量不會(huì)很大了。

基本上分析就到此為止了,talk is cheap, show me code,下面就是代碼實(shí)現(xiàn)了。

代碼實(shí)現(xiàn)

編輯模塊

文字輸入框的Cell實(shí)現(xiàn)

下面是文字輸入框的Cell的主要代碼,包含了

初始設(shè)置文字編輯Cell的高度、文字內(nèi)容、是否顯示Placeholder

在?UITextViewDelegate?回調(diào)方法?textViewDidChange?中處理Cell的高度自動(dòng)拉伸

刪除的回調(diào)方法中處理前面刪除和后面刪除,刪除回調(diào)的代理方法是繼承?UITextView?重寫?deleteBackward?方法進(jìn)行的回調(diào),具體的可以額查看?MMTextView?這個(gè)類的實(shí)現(xiàn),很簡(jiǎn)單的一個(gè)實(shí)現(xiàn)。

顯示圖片Cell的實(shí)現(xiàn)

下面顯示圖片Cell的實(shí)現(xiàn),主要包含了

初始設(shè)置文字編輯Cell的高度、圖片顯示內(nèi)容

在?UITextViewDelegate?回調(diào)方法?shouldChangeTextInRange?中處理?yè)Q行和刪除,這個(gè)地方的刪除和Text編輯的Cell不一樣,所以在這邊做了特殊的處理,具體看一看?shouldChangeTextInRange?這個(gè)方法的處理方式。

處理圖片上傳的進(jìn)度回調(diào)、失敗回調(diào)、成功回調(diào)

圖片上傳模塊

圖片上傳模塊中,上傳的元素和上傳回調(diào)抽象了對(duì)應(yīng)的協(xié)議,圖片上傳模塊是一個(gè)單利的管理類,管理進(jìn)行中的上傳元素和排隊(duì)中的上傳元素,

圖片上傳的元素和上傳回調(diào)的抽象協(xié)議

圖片上傳的管理類

圖片上傳使用的是?NSURLSessionUploadTask?類處理

在?completionHandler?回調(diào)中處理結(jié)果

在NSURLSessionDelegate?的方法?URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:?中處理上傳進(jìn)度

在NSURLSessionDelegate?的方法?URLSession:task:didCompleteWithError:?中處理失敗

上傳管理類的關(guān)鍵代碼如下:

圖片上傳的回調(diào)會(huì)通過(guò)?UploadItemCallBackProtocal?協(xié)議的實(shí)現(xiàn)方法回調(diào)到圖片編輯的模型中,更新對(duì)應(yīng)的數(shù)據(jù)。圖片編輯的數(shù)據(jù)模型是?MMRichImageModel?,該模型實(shí)現(xiàn)了?UploadItemProtocal?和?UploadItemCallBackProtocal?協(xié)議,實(shí)現(xiàn)?UploadItemCallBackProtocal?的方法更新數(shù)據(jù)模型的同時(shí),會(huì)通過(guò)delegate通知到Cell更新進(jìn)度和失敗成功的狀態(tài)。 關(guān)鍵的實(shí)現(xiàn)如下

內(nèi)容處理模塊

最終是要把內(nèi)容序列化然后上傳到服務(wù)端的,我們的序列化方案是轉(zhuǎn)換為HTML,內(nèi)容處理模塊主要包含了以下幾點(diǎn):

生成HTML格式的內(nèi)容

驗(yàn)證內(nèi)容是否有效,判斷圖片時(shí)候全部上傳成功

壓縮圖片

保存圖片到本地

這部分收尾的工作比較的簡(jiǎn)單,下面是實(shí)現(xiàn)代碼:

總結(jié)

這個(gè)功能從選型定型到實(shí)現(xiàn)大概花費(fèi)了3天的時(shí)間,因?yàn)闀r(shí)間原因,有很多地方優(yōu)化的不到位,如果看官有建議意見(jiàn)希望給我留言,我會(huì)繼續(xù)完善,或者你有時(shí)間歡迎加入這個(gè)項(xiàng)目,可以一起做得更好,代碼開源看下面的鏈接。

代碼托管位置

客戶端代碼開源托管地址:MMRichTextEdit

java實(shí)現(xiàn)的文件服務(wù)器代碼開源托管地址:javawebserverdemo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 最好的我們熱播,喚起了眾票青春回憶,曾經(jīng)的那個(gè)他,或者曾經(jīng)沒(méi)能成為那個(gè)他的他,現(xiàn)在怎么樣? 也許都沒(méi)有,那么曾經(jīng)的...
    小二ivy閱讀 496評(píng)論 0 1
  • 孩子書包落在樓下車筐里,沒(méi)有拿上樓 。他發(fā)脾氣,不寫作業(yè),把我在樓上給他找的紙筆橡皮都甩在地上。 我耐住...
    白羽1413閱讀 532評(píng)論 1 1
  • 今年春節(jié)計(jì)劃去韓國(guó)旅游,放松一下身心,同時(shí)增長(zhǎng)一下見(jiàn)識(shí)。本來(lái)想就當(dāng)休閑游,也不做設(shè)么攻略了,但是想想,至少基本的交...
    浮云狒閱讀 601評(píng)論 0 0
  • 其實(shí)我們都不想做一個(gè)安于現(xiàn)狀的人,所以學(xué)會(huì)升級(jí)自己的生命尺度是很關(guān)鍵的,不斷的去提升自己,不斷的去努力,不斷的讓...
    合肥李風(fēng)麗閱讀 217評(píng)論 0 0
  • 質(zhì)樸食材意真誠(chéng),兒食母做好心情。 一年共進(jìn)家常飯,四海奔波思更濃。
    思索者閱讀 193評(píng)論 2 2

友情鏈接更多精彩內(nèi)容