效果直接放這了,先看效果再上菜。
目錄:
一.功能實(shí)現(xiàn)說明
二.實(shí)現(xiàn)效果核心代碼片段
三.拓展思考方面細(xì)節(jié)
一.功能實(shí)現(xiàn)說明
1.遇到的問題說明
不知大家有沒有遇到這樣的功能剑逃,即如開篇的圖所示,在一個(gè)輸入框中去實(shí)現(xiàn)放按鈕的效果官辽,也就是在同一個(gè)輸入框無論是TextFeild還是TextView中蛹磺,支持放按鈕,也支持通過輸入信息去搜索的功能實(shí)現(xiàn)同仆。
2.對(duì)該問題的思考?xì)v程并拋出新問題
遇到這樣的問題萤捆,首先我們的解決方案有2個(gè),1個(gè)是把輸入框作為假輸入框俗批,即按鈕是真的鳖轰,輸入框中的要輸入的光標(biāo)是假的;
還有一個(gè)方案是把按鈕做成假的扶镀,輸入框是真的蕴侣,然后把假按鈕放入輸入框內(nèi),即可臭觉。
我的思考是昆雀,如果采用方案1,首先如果輸入框是固定不動(dòng)的話蝠筑,那么做一個(gè)光標(biāo)閃一下現(xiàn)一下的動(dòng)畫狞膘,然后做一個(gè)自定義鍵盤配合監(jiān)聽其代理方法,即可解決這個(gè)問題什乙。但是挽封,光標(biāo)可能會(huì)移動(dòng),且隨時(shí)插入按鈕之間臣镣,此時(shí)該方案就很明顯要被pass掉了辅愿。
3.解決問題采取的方案
那么智亮,如果采用方案二,假按鈕從何而來点待。答案不言自明阔蛉,即富文本中來操作。因?yàn)樽鳛橐粋€(gè)按鈕要實(shí)現(xiàn)點(diǎn)擊和點(diǎn)擊后不同的效果癞埠,則可以用富文本的
NSLinkAttributeName屬性状原,來設(shè)置不同顏色從而實(shí)現(xiàn)按鈕的點(diǎn)擊效果。
好了苗踪,方案通過思考并驗(yàn)證后颠区,那么如何實(shí)現(xiàn)呢,接下來我們接著說說這塊的思考通铲。
二.實(shí)現(xiàn)效果核心代碼片段
1.整體框架說明實(shí)現(xiàn)
首先毕莱,先來介紹說明一下整體實(shí)現(xiàn)該方案的框架,主要涉及2個(gè)管理類测暗,即DDRTSelectedManager 和 DDRTGroupItemManager央串。
其中DDRTSelectedManager主要實(shí)現(xiàn)的是在前一個(gè)頁面中列表多選的功能磨澡,其實(shí)與本節(jié)富文本研究是作為配角而存在碗啄;
而DDRTGroupItemManager則是實(shí)現(xiàn)本節(jié)中TextView中富文本點(diǎn)擊事件的核心管理類。
接下來稳摄,我們逐個(gè)來說說稚字!
2.擴(kuò)展選擇實(shí)現(xiàn)類邏輯
首先,先來說說DDRTSelectedManager中主要實(shí)現(xiàn)的是前面頁面選項(xiàng)卡選擇的問題厦酬,即類似于購(gòu)物車的問題胆描。
如下代碼所示:
// 獲取數(shù)據(jù)源的方法
-(void)initAllDatas;
// 更新某一行選中狀態(tài)
-(NSMutableArray *)updateItemManager:(NSIndexPath *)indexPath;
// 遍歷到所有選中的數(shù)據(jù)
-(NSMutableArray *)getAllSelectedDatas;
主要目標(biāo)是通過數(shù)據(jù)源來解決TableViewCell的重用問題,只是通過改變數(shù)據(jù)源來實(shí)現(xiàn)列表的及時(shí)刷新實(shí)現(xiàn)仗阅。
3.Model包裝類的實(shí)現(xiàn)
在Model包裝類中的實(shí)現(xiàn)方式昌讲,是通過在Model普通屬性之外,定義如下所示的屬性
// 當(dāng)前字符所處字符串實(shí)際開始位置
@property(nonatomic,assign)NSInteger nameLocation;
// 名字的實(shí)際長(zhǎng)度
@property(nonatomic,assign)NSInteger nameLength;
// 名字的實(shí)際長(zhǎng)度 + 3(2個(gè)空格 + 1個(gè)逗號(hào))
@property(nonatomic,assign)NSInteger nameAppendAfterLength;
// 是否被點(diǎn)擊减噪,即將刪除的情形
@property(nonatomic,assign)BOOL isClick;
從而實(shí)現(xiàn)所有群組數(shù)組中記錄的Mode中當(dāng)前的name字段拼接后其所處的字符串中的長(zhǎng)度和位置短绸,以方便后面渲染到頁面中可以精確控制每個(gè)假按鈕的分隔點(diǎn)。
4.群組成員按鈕管理類的實(shí)現(xiàn)
很明顯群組成員按鈕管理類是本次實(shí)現(xiàn)功能的核心代碼,如下所示朝卒,
4.1.首先我們先來分析一下证逻,在該管理類中實(shí)現(xiàn)的幾個(gè)接口方法。
// 選中的狀態(tài)
-(void)selectedItemState:(DDRTGroupMemberModel *)model;
// 未選中的狀態(tài)
-(void)cancleItemState:(DDRTGroupMemberModel *)model;
-(void)addGroupItem:(DDRTGroupMemberModel *)model;
-(void)deleteGroupItem:(DDRTGroupMemberModel *)model;
// 返回表示有沒有可刪除的內(nèi)容抗斤,沒有的話囚企,把最后一個(gè)選中,并返回NO丈咐,默認(rèn)返回YES
-(BOOL)deleteSelectedGroupArr;
// 根據(jù)傳入的下標(biāo),獲得到對(duì)應(yīng)數(shù)組的位置
-(NSInteger)indexOfGroupLoctaion:(NSInteger)location;
// 獲取被處理過的富文本字符串
-(NSMutableAttributedString *)getAttributeAllMemberStr;
// 第一次進(jìn)來時(shí)洞拨,更新所有數(shù)據(jù)源的點(diǎn)擊狀態(tài)為普通狀態(tài)
-(void)updateAllItemStateToNomal;
4.2.富文本字符串處理的細(xì)節(jié)核心代碼
這里每次更新成員數(shù)組狀態(tài)扯罐,如個(gè)數(shù),群成員按鈕狀態(tài)時(shí)烦衣,都要調(diào)用該方法歹河,去重新獲取被渲染后的富文本。
圖中主要注意2點(diǎn)花吟,即第一點(diǎn)是要區(qū)分按鈕的點(diǎn)擊位置和實(shí)際所占用位置的不同秸歧,因?yàn)榫唧w實(shí)現(xiàn)時(shí),按鈕和按鈕之間還會(huì)有其他字符作為分隔衅澈;以及最后一個(gè)按鈕實(shí)現(xiàn)時(shí)是沒有字符分隔的键菱。
第二點(diǎn)是對(duì)按鈕的2種狀態(tài)進(jìn)行區(qū)分顯示的邏輯。
// 獲取被處理過的富文本字符串
-(NSMutableAttributedString *)getAttributeAllMemberStr {
// 清空字符串
[self.allGroupMemberStr replaceCharactersInRange:NSMakeRange(0, self.allGroupMemberStr.mutableString.length) withString:@""];
if (self.groupMemberArr && self.groupMemberArr.count) {
NSInteger totalLocation = 0;
for (int index = 0; index < self.groupMemberArr.count; index ++) {
DDRTGroupMemberModel *memberModel = self.groupMemberArr[index];
memberModel.nameLocation = totalLocation;
NSString *nameStr;
NSMutableAttributedString *attrStr;
BOOL isLast = NO;
if (index == self.groupMemberArr.count - 1) {// 最后一位的話今布,不拼接對(duì)應(yīng)的字符串
nameStr = memberModel.personName;
isLast = YES;
}
else {
nameStr = [NSString stringWithFormat:@"%@%@",memberModel.personName,kAppendStrSign];
isLast = NO;
}
totalLocation = totalLocation + nameStr.length;
if (memberModel.isClick == YES) {
attrStr = [self getSubStringClick:nameStr andFont:kRichTextViewFont andIsLast:isLast];
}
else {
attrStr = [self getSubStringNormal:nameStr andFont:kRichTextViewFont andIsLast:isLast];
}
[self.allGroupMemberStr appendAttributedString:attrStr];
}
}
return self.allGroupMemberStr;
}
這樣一解釋后经备,大家是不是明顯清晰多了哈!
4.3.如何監(jiān)聽到點(diǎn)擊點(diǎn)擊位置改變其按鈕狀態(tài)
如下代碼所示部默,在textView點(diǎn)擊鏈接的代理方法中侵蒙,通過點(diǎn)擊傳入點(diǎn)擊位置,如下的location傅蹂,
到我們的DDRTGroupItemManager 中的indexOfGroupLoctaion方法獲得到其點(diǎn)擊位置在群成員數(shù)組中的下標(biāo)纷闺,然后去更改對(duì)應(yīng)的富文本字符串即可。
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange {
DDLog(@"%lu ------ %lu",(unsigned long)characterRange.location,(unsigned long)characterRange.length);
if (textView == self.groupTextView) {
NSInteger location = [[NSNumber numberWithUnsignedInteger:characterRange.location] integerValue];
NSInteger clickIndex = [self.groupItemManager indexOfGroupLoctaion:location];
if (clickIndex < self.groupItemManager.groupMemberArr.count) {
DDRTGroupMemberModel *model = self.groupItemManager.groupMemberArr[clickIndex];
if (model.isClick == YES) {
[self.groupItemManager cancleItemState:model];
}
else {
[self.groupItemManager selectedItemState:model];
}
self.groupTextView.attributedText = [self.groupItemManager getAttributeAllMemberStr];
}
}
return YES;
}
indexOfGroupLoctaion方法實(shí)現(xiàn)如下
NSInteger nowIndex = 0;
if (self.groupMemberArr && self.groupMemberArr.count) {
for (int index = 0; index < self.groupMemberArr.count; index ++ ) {
DDRTGroupMemberModel *model = self.groupMemberArr[index];
if (model.nameLocation >= location) {
nowIndex = index;
break;
}
}
}
return nowIndex;
這么多方法看下來份蝴,注釋寫的也很明白犁功,其實(shí)核心點(diǎn)主要是對(duì)每個(gè)成員選項(xiàng)卡按鈕的增刪改查工作,以及準(zhǔn)確定位到當(dāng)前選擇的成員的功能實(shí)現(xiàn)婚夫。
具體里面內(nèi)容怎么實(shí)現(xiàn)呢浸卦,詳情可看本文末尾處的連接。
原創(chuàng)不易案糙,如果幫助到了朋友們限嫌,歡迎star哈!
5.實(shí)現(xiàn)的相關(guān)功能匯總
這里就對(duì)該上面4里面所涉及的所有按鈕功能進(jìn)行一個(gè)簡(jiǎn)要說明:
5.1.在群組中的每個(gè)成員作為一個(gè)假按鈕處于TextView控件中展示侍筛;
5.2.當(dāng)用戶點(diǎn)擊對(duì)應(yīng)按鈕時(shí)萤皂,選中取消選中功能實(shí)現(xiàn)
5.3.當(dāng)用戶點(diǎn)擊最后一個(gè)按鈕末尾處,可看到光標(biāo)顯示(但目前無法輸入文字)匣椰。此時(shí)點(diǎn)擊鍵盤刪除圖標(biāo)時(shí)裆熙,倘若有按鈕被選中,則選中的按鈕全部倍刪除,未選中的按鈕位置依次向左移動(dòng)入录;倘若按鈕沒有被選中蛤奥,則默認(rèn)點(diǎn)擊一次刪除圖標(biāo),最后一個(gè)按鈕被選中僚稿,再點(diǎn)擊一次刪除圖標(biāo)刪除最后一個(gè)按鈕凡桥。
5.4.用戶可以在成員列表界面選擇對(duì)應(yīng)的成員后跳轉(zhuǎn)到富文本成員頁面。而在富文本成員頁面中刪除對(duì)應(yīng)的成員后蚀同,點(diǎn)擊返回或者右側(cè)的添加按鈕缅刽,其成員列表中該成員也被刪除。
即實(shí)現(xiàn)成員列表和富文本成員頁面的實(shí)時(shí)更新功能蠢络。
三.拓展思考方面細(xì)節(jié)
1.解題思維說明
首先衰猛,需要說明的是,完成對(duì)本篇富文本功能實(shí)現(xiàn)流程后刹孔,我們會(huì)對(duì)底層的UI如按鈕啡省,Label等實(shí)現(xiàn)有了更清晰的理解。更接近其底層實(shí)現(xiàn)原理髓霞。
另外一層的思考是對(duì)于Model管理類的理解需要進(jìn)一步加深卦睹,即其主要處理的不僅僅是與數(shù)據(jù)有關(guān),也有很多業(yè)務(wù)邏輯方库。特別是如同本文所示的數(shù)據(jù)之間層層嵌套结序,且層與層之間還略有不同,如按鈕有2種狀態(tài)薪捍,最后一個(gè)按鈕的沒有分隔符笼痹,每個(gè)按鈕的點(diǎn)擊范圍和渲染范圍不同等等邏輯時(shí)配喳。
我們應(yīng)轉(zhuǎn)變思路酪穿,通過對(duì)Model的共同數(shù)據(jù)可以處理,那么不同的數(shù)據(jù)也可以通過Model來記錄晴裹。之后再配合數(shù)據(jù)源管理類來實(shí)現(xiàn)數(shù)據(jù)的改變被济,然后讓底層UI去根據(jù)數(shù)據(jù)改變刷新為我們需要的UI的思考邏輯。
2.回到基本面向?qū)ο笸粐?/strong>
對(duì)于面向?qū)ο蟮睦斫饨牛覀冃枰M(jìn)一步突圍發(fā)展只磷,擴(kuò)張基本面。即面向?qū)ο笫窃谖覀兘忸}過程中必備的思想泌绣,是需要我們深入其核心去理解钮追。
比如像本篇中的面向Model和面向2個(gè)管理類的處理邏輯。每一個(gè)管理類去管理對(duì)應(yīng)的分工阿迈。面向自己所解決的對(duì)象元媚,去實(shí)現(xiàn)對(duì)應(yīng)的方法。
而我們的控制器呢,只是拿其優(yōu)勢(shì)為其所用刊棕,最終實(shí)現(xiàn)一個(gè)個(gè)功能塊炭晒。
功能塊就是我們的面向?qū)ο蟮膶?duì)象,那么我們的面向?qū)ο笮枰鸱纸o其他擅長(zhǎng)該類的管理者甥角,Model网严,幫助類,第三方庫(kù)等去實(shí)現(xiàn)嗤无,這就是我們的面向?qū)ο笏枷搿?/strong>
好了震束,本篇廢話有點(diǎn)小多,最后呢附上本文代碼的倉(cāng)庫(kù)地址:我的富文本之DDRichTextDemo
以及上一篇關(guān)于富文本功能實(shí)現(xiàn)的代碼地址:iOS富文本實(shí)現(xiàn)(-):私密閱讀效果
歡迎大家評(píng)論區(qū)交流哦当犯!