写在前面
文章详情页 特殊文字是一次性删除的 点击特殊文字,会有响应 折行效果公司最近写了一个社交的功能模块,有以下几个功能
1.发布文章
2.转发文章
3.评论文章
4.回复评论
5.文章列表
基本上就是公司一切仿照微博的写,然后我就一直写啊写~
这个其实是比较简单的功能,稍微麻烦一点的是富文本技术,
1.如列表页点击@张同学
,点击$深圳股指(000001)$
,#我是个话题#
等等
2.要判断文章是不是超过七行文字,如果超过了,那么我们要有...全文
来结尾
3.在发布页面,当打出来特殊的字符,如@张同学
,点击$深圳股指(000001)$
等,将他们作为一个整体,然后删除的时候,将一个整体删除
4.如果是超链接的话,那么可能有折行问题,如果点击了某个折行,整个超链接都要响应,如何处理?
过去因为没有处理过这些,所以一点点的学习,这里真心挺多坑,最后很多API都是底层的,找起来很累,但是好在实现了,在网上找了题目的问题,但是没有,很朋友一起讨论,给了我一个临时的方法,就是如果文本超过了七行,直接将
...全文
贴到第七行上,都不用看,这样很简单,但是效果很不好,所以我就用了一天的时间探索这个问题,好在最后,找出了答案
这个项目使用了
TextKit
的相关技术,然后我才看到了有好多底层的东西,过去习惯写UI了,所以真心没有研究过底层,之前有看过YYKit
的东西,但是没有自己写,所以最近趁着学习textkit,趁机写一个像锤子便签的那个图文混排的demo,并且,锤子便签支持富文本,这个真心赞,所以决定好好学习一下~
说点正题
思维导图"...全文"这个功能相对比较麻烦,因为他可能要考虑好几种情况,最后还要有api才能实现,我因为刚刚接触,所以很多地方是不确定的,所以如果有同行知道更好的解决方案,麻烦告诉我一下,我也好好的学习。一会pod出来的代码,是没有经过重构的,性能的就不考虑了,一会会写出来具体的优化思路。将来有时间了,我才去重构一下,然后再拿出来一起看看
图例14.为什么要获取第七行的frame
先说说思路
1.获取第七行文字
2.生成一个控件 "...全文",宽度50dx
3.判断最后一个字符是不是在富文本中,如果在就删除他,和4放在一起使用
4.然后去判断删除一个字节之后的第七行文字+"...全文" <textView宽度
(但是如果刚才删除的最后一个字符在特殊文本之内,如@张同学
,点击$深圳股指(000001)$
,那么我们应该直接将这个字符全都删除,再去判断5是否成立),for循环,知道for的次数是第七行文字的length
6.for完毕之后,我们获取出一个第七行最合适的文本,那么我们应该去将这个最合适的文本替换给 textView.attributedText的第七行文字,然后去更新一下...全文的x值
7.一定要注意,就是防止循环引用,很多属性都要给status,然后拿到了才不会有问题,我这写在view中,所以view经常会被复用,导致了一些属性有问题,切记
具体实现的代码如下:
self.textLabel.frame = originalFrame.textFrame;
//设置超过一定行数,就截断的功能,防止循环引用
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{
//删除数据了
[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);
//保存一个当前最后一行数据,就是个临时的东西
__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;
}
}
}
图1.首页列表显示的样子
图2.点击进入详情页
“...全文”的位置都是正确的 同一个cell,内部和外边比较,截取的都是一个特殊的字符串的尺寸 普通文本截取的时候也是正确合理的这两张图片要说明什么?就是在数据返回来的时候,我们给他排班,最后一行在详情页就可以看到了,这种情况下,添加『..全文』是成立的,所以就像图1.那样,当时他确实错误的,所以应该直接去判断最后一个字符是不是在富文本中,然后再判断如果加了"...全文",是不是可以符合宽度的要求
项目缺点
第一.i值可能多次使用,如果删除特殊的字符串的时候,如@zhang
,那么下一次i = i-zhang.length
第二.每一次都要计算一下,应该是去缓存下来,"...全文"的尺寸只去计算一次
第三.这个代码很垃圾,所以我们有时间重构一下,封装一个类
这个是我自己写的东西,感觉很多的地方是不好的,性能什么的,如果诸位知道如何做,一定要告诉我哈~一起学习进步
今天领导突然要更改文字的大小和文字间距,我就蒙了,然后去这里"...全文"的功能就乱了,然后去修改
/**
*七行文字的问题,当字体是14dx,行间距是4dx(设计图中是16px,对应到项目中是4dx,因为有上下行的问题,所以除以4)的时候;
* textView适合的高度(能在textView中显示7行文字的高度)
* 如果文字小于七行,也是要+2dx(已经处理了),如果大于的时候,也处理了(在SEStatusLableMaxRowHeight中最合适的+2了)
* SEStatusLableMaxRowHeight 数据是如何计算的?通过计算获取textSize的高度+2,这个是第七行和文字比较的系数,比较是合理的,但是此时textView如果是8行的时候,通过layoutManager获取不到第七行文字,要再去+2才行,也是就是+4,(我说的是14dx,4dx的情况,如果是16号字体不确定,但是核心是保证获取到第七行文字)
*7行文字textView适合的高度 如何确定?当前文字高度+2 (我说的是14dx,4dx的情况)
* 字体大小 行间距 第7行文字的高度 第8行文字的高度 7行文字textView适合的高度 最后一行文字的frame 比较第七行的系数
* 14dx 4dx 141 161.6 145 (0,129,355,145) 145
* 16dx 4dx 157.6 180.7 160 (0,136,355,145) 162
*/
修改后的样式
网友评论