寫在前面
公司最近寫了一個社交的功能模塊,有以下幾個功能
1.發(fā)布文章
2.轉發(fā)文章
3.評論文章
4.回復評論
5.文章列表
基本上就是公司一切仿照微博的寫近她,然后我就一直寫啊寫~
這個其實是比較簡單的功能,稍微麻煩一點的是富文本技術愤兵,
1.如列表頁點擊@張同學
铁蹈,點擊$深圳股指(000001)$
,#我是個話題#
等等
2.要判斷文章是不是超過七行文字拉队,如果超過了弊知,那么我們要有...全文
來結尾
3.在發(fā)布頁面,當打出來特殊的字符粱快,如@張同學
秩彤,點擊$深圳股指(000001)$
等,將他們作為一個整體事哭,然后刪除的時候漫雷,將一個整體刪除
4.如果是超鏈接的話,那么可能有折行問題鳍咱,如果點擊了某個折行降盹,整個超鏈接都要響應,如何處理谤辜?
過去因為沒有處理過這些澎现,所以一點點的學習,這里真心挺多坑每辟,最后很多API都是底層的剑辫,找起來很累,但是好在實現(xiàn)了渠欺,在網(wǎng)上找了題目的問題妹蔽,但是沒有,很朋友一起討論,給了我一個臨時的方法胳岂,就是如果文本超過了七行编整,直接將
...全文
貼到第七行上,都不用看乳丰,這樣很簡單掌测,但是效果很不好,所以我就用了一天的時間探索這個問題产园,好在最后汞斧,找出了答案
這個項目使用了
TextKit
的相關技術,然后我才看到了有好多底層的東西什燕,過去習慣寫UI了粘勒,所以真心沒有研究過底層,之前有看過YYKit
的東西屎即,但是沒有自己寫庙睡,所以最近趁著學習textkit,趁機寫一個像錘子便簽的那個圖文混排的demo技俐,并且乘陪,錘子便簽支持富文本,這個真心贊雕擂,所以決定好好學習一下~
說點正題
"...全文"這個功能相對比較麻煩暂刘,因為他可能要考慮好幾種情況,最后還要有api才能實現(xiàn)捂刺,我因為剛剛接觸谣拣,所以很多地方是不確定的,所以如果有同行知道更好的解決方案族展,麻煩告訴我一下森缠,我也好好的學習。一會pod出來的代碼仪缸,是沒有經(jīng)過重構的贵涵,性能的就不考慮了,一會會寫出來具體的優(yōu)化思路恰画。將來有時間了宾茂,我才去重構一下,然后再拿出來一起看看
先說說思路
1.獲取第七行文字
2.生成一個控件 "...全文"拴还,寬度50dx
3.判斷最后一個字符是不是在富文本中跨晴,如果在就刪除他,和4放在一起使用
4.然后去判斷刪除一個字節(jié)之后的第七行文字+"...全文" <textView寬度
(但是如果剛才刪除的最后一個字符在特殊文本之內片林,如@張同學
端盆,點擊$深圳股指(000001)$
怀骤,那么我們應該直接將這個字符全都刪除,再去判斷5是否成立)焕妙,for循環(huán)蒋伦,知道for的次數(shù)是第七行文字的length
6.for完畢之后,我們獲取出一個第七行最合適的文本焚鹊,那么我們應該去將這個最合適的文本替換給 textView.attributedText的第七行文字痕届,然后去更新一下...全文的x值
7.一定要注意,就是防止循環(huán)引用末患,很多屬性都要給status研叫,然后拿到了才不會有問題,我這寫在view中阻塑,所以view經(jīng)常會被復用蓝撇,導致了一些屬性有問題果复,切記
具體實現(xiàn)的代碼如下:
self.textLabel.frame = originalFrame.textFrame;
//設置超過一定行數(shù)陈莽,就截斷的功能,防止循環(huán)引用
if(status.isNeed){
BOOL hiddenMore = (self.originalFrame.textFrame.size.height < SEStatusLableMaxRowHeight);
self.textLabel.moreView.hidden = hiddenMore;
[self.textLabel layoutSubviews];
if (!hiddenMore && (self.originalFrame.allTextX == 0)) {
CGRect lastRowFrame = CGRectMake(0, 108, ScreenWidth - 2*SEStatusCellInset, originalFrame.textFrame.size.height);
NSRange lastRowRangeInAllText = [self.textLabel.textView.layoutManager glyphRangeForBoundingRect:lastRowFrame inTextContainer:self.textLabel.textView.textContainer];
//通過lastRowRangeInAllText獲取最后一行的文字
NSString *lastRowString = [self.textLabel.textView.attributedText.string substringWithRange:lastRowRangeInAllText];
//最后一行文字的size
CGSize lastRowTextSize = [self fetchTextWidthWithTextString:lastRowString andFrame:lastRowFrame];
BOOL lastRowTextFit = [self judgeWhetherIsRightWithLastRowSize:lastRowTextSize andFrame:lastRowFrame];
if(lastRowTextFit){ //50是"...全部"的寬度,10虽抄,文字和 ...全文 的間隔
//判斷最后一個字符的loction是不是在某個富文本之中
//如果在其中走搁,只能是是最后,為真迈窟,其他為假
//如果不在私植,可以直接添加了
[self.textLabel.attributedText enumerateAttributesInRange:NSMakeRange(0, self.textLabel.attributedText.length)
options:NSAttributedStringEnumerationReverse usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
if(self.originalFrame.status.stopEnumerate){
// return ;
return ;
}
NSString *linkText = attrs[SELinkText];
NSInteger linkLoctionInLastRowText = range.location-lastRowRangeInAllText.location;
if(linkText == nil){
//如果符合,我們直接給 ... 全部 一個x值就好了
self.originalFrame.allTextX = lastRowTextSize.width + SEAllTextMarginToRemindText;
self.textLabel.moreX = self.originalFrame.allTextX;
return;
}
if(linkLoctionInLastRowText<0){
return;
}
//保存最有一個富文本,也是有問題的车酣,如果在第九行曲稼,也是白搭
//看看一共包含
NSInteger numElements = range.length/linkText.length;
for(int j = 0;j<numElements;j++){
//臨時的位置
NSInteger tempLinkLoctionInLastRowText = linkLoctionInLastRowText + linkText.length*j;
NSRange linkRange = NSMakeRange(tempLinkLoctionInLastRowText, linkText.length);
if(linkRange.location != NSNotFound && linkRange.length){
NSInteger maxRangeLoc = NSMaxRange(linkRange)-1;
NSInteger lastWordLoc = lastRowString.length-1;
if(maxRangeLoc == lastWordLoc){
//如果符合,我們直接給 ... 全部 一個x值就好了
self.originalFrame.allTextX = lastRowTextSize.width + SEAllTextMarginToRemindText;
self.textLabel.moreX = self.originalFrame.allTextX;
return ;
}else{
//刪除數(shù)據(jù)了
[self deleteLastWordWithLastRowString:lastRowString
lastRowRangeInAllText:lastRowRangeInAllText
lastRowFrame:lastRowFrame];
}
}
//感覺這里要做點事情湖员,但是沒有想好
}
}
];
}
else{
[self deleteLastWordWithLastRowString:lastRowString
lastRowRangeInAllText:lastRowRangeInAllText
lastRowFrame:lastRowFrame];
}
}
else if (!hiddenMore && self.originalFrame.allTextX != 0){
self.textLabel.moreX = self.originalFrame.allTextX;
}
}
else{
self.textLabel.moreView.hidden = YES;
}
self.textLabel.moreView.hidden = (self.originalFrame.allTextX==0);
//刪除最后一個字符贫悄,判斷是否符合要求
- (void)deleteLastWordWithLastRowString:(NSString *)lastRowString
lastRowRangeInAllText:(NSRange)lastRowRangeInAllText
lastRowFrame:(CGRect)lastRowFrame
{
for(int i=1;i<=lastRowString.length;i++){
//往前減去一個字符,判斷是不是特殊字符娘摔,如果是特殊字符窄坦,算出將整個特俗字符刪除,再去計算凳寺,判斷
__block NSRange lastWordRangeInRow = NSMakeRange(lastRowString.length-i, 1);
//保存一個當前最后一行數(shù)據(jù)鸭津,就是個臨時的東西
__block NSString *tempLastString = nil;
[self.textLabel.attributedText enumerateAttributesInRange:NSMakeRange(0, self.textLabel.attributedText.length)
options:0 usingBlock:^(NSDictionary *attrs, NSRange range, BOOL *stop) {
NSString *linkText = attrs[SELinkText];
//如果沒有富文本,反悔肠缨,然后直接長度減一
if (linkText == nil) return;
NSInteger linkLoctionInLastRowText = range.location-lastRowRangeInAllText.location;
if(linkLoctionInLastRowText<0)
return;
//看看一共包含
NSInteger numElements = range.length/linkText.length;
for(int j = 0;j<numElements;j++){
//最有一個位置
NSInteger tempLinkLoctionInLastRowText = linkLoctionInLastRowText + linkText.length*j;
NSRange linkRange = NSMakeRange(tempLinkLoctionInLastRowText, linkText.length);
if(linkRange.location != NSNotFound && linkRange.length){
BOOL same = NSLocationInRange(lastWordRangeInRow.location, linkRange);
if(same){
lastWordRangeInRow = linkRange;
break;
}else if(NSEqualRanges(lastWordRangeInRow, linkRange)){
lastWordRangeInRow = NSMakeRange(linkRange.location-1, 1);
break;
}else{
//啥也不干
}
}
}
}];
//將最后的字符刪除
tempLastString = [lastRowString substringToIndex:lastWordRangeInRow.location];
//計算一下新的字符串是否符合『...全文』的寬度
CGSize tempSize = [self fetchTextWidthWithTextString:tempLastString andFrame:lastRowFrame];
BOOL tempSmaller = [self judgeWhetherIsRightWithLastRowSize:tempSize andFrame:lastRowFrame];
if(tempSmaller){ //可以放下
//獲取最后一個字符以后的文字的range
NSInteger lastWordLoctionInTextView = lastRowRangeInAllText.location+tempLastString.length;
NSInteger willRemoveTextLength = self.textLabel.textView.attributedText.length - lastWordLoctionInTextView;
NSRange willRemoveRange = NSMakeRange(lastWordLoctionInTextView, willRemoveTextLength);
NSMutableAttributedString *mutableString = [[NSMutableAttributedString alloc] initWithAttributedString:self.textLabel.textView.attributedText];
//使用刪除或者替換 delete...都可以
[mutableString replaceCharactersInRange:willRemoveRange withString:@""];
self.textLabel.textView.attributedText = mutableString;
self.originalFrame.status.attributedText = mutableString;
//給"...全部"一個frame
self.originalFrame.allTextX = tempSize.width + SEAllTextMarginToRemindText;
self.textLabel.moreX = self.originalFrame.allTextX;
self.originalFrame.status.stopEnumerate = YES;
break;
}
else{
continue;
}
}
}
這兩張圖片要說明什么逆趋?就是在數(shù)據(jù)返回來的時候,我們給他排班晒奕,最后一行在詳情頁就可以看到了父泳,這種情況下般哼,添加『..全文』是成立的,所以就像圖1.那樣惠窄,當時他確實錯誤的蒸眠,所以應該直接去判斷最后一個字符是不是在富文本中,然后再判斷如果加了"...全文"杆融,是不是可以符合寬度的要求
項目缺點
第一.i值可能多次使用脾歇,如果刪除特殊的字符串的時候,如@zhang
,那么下一次i = i-zhang.length
第二.每一次都要計算一下蒋腮,應該是去緩存下來,"...全文"的尺寸只去計算一次
第三.這個代碼很垃圾,所以我們有時間重構一下藕各,封裝一個類
這個是我自己寫的東西池摧,感覺很多的地方是不好的,性能什么的激况,如果諸位知道如何做作彤,一定要告訴我哈~一起學習進步
今天領導突然要更改文字的大小和文字間距,我就蒙了乌逐,然后去這里"...全文"的功能就亂了竭讳,然后去修改
/**
*七行文字的問題,當字體是14dx浙踢,行間距是4dx(設計圖中是16px绢慢,對應到項目中是4dx,因為有上下行的問題洛波,所以除以4)的時候胰舆;
* textView適合的高度(能在textView中顯示7行文字的高度)
* 如果文字小于七行,也是要+2dx(已經(jīng)處理了)蹬挤,如果大于的時候缚窿,也處理了(在SEStatusLableMaxRowHeight中最合適的+2了)
* SEStatusLableMaxRowHeight 數(shù)據(jù)是如何計算的?通過計算獲取textSize的高度+2闻伶,這個是第七行和文字比較的系數(shù)滨攻,比較是合理的,但是此時textView如果是8行的時候蓝翰,通過layoutManager獲取不到第七行文字光绕,要再去+2才行,也是就是+4,(我說的是14dx畜份,4dx的情況诞帐,如果是16號字體不確定,但是核心是保證獲取到第七行文字)
*7行文字textView適合的高度 如何確定爆雹?當前文字高度+2 (我說的是14dx停蕉,4dx的情況)
* 字體大小 行間距 第7行文字的高度 第8行文字的高度 7行文字textView適合的高度 最后一行文字的frame 比較第七行的系數(shù)
* 14dx 4dx 141 161.6 145 (0,129,355,145) 145
* 16dx 4dx 157.6 180.7 160 (0,136,355,145) 162
*/