微博的角标"...全文"功能是如何实现的

写在前面

文章详情页
特殊文字是一次性删除的
点击特殊文字,会有响应
折行效果

公司最近写了一个社交的功能模块,有以下几个功能
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.点击进入详情页

这两张图片要说明什么?就是在数据返回来的时候,我们给他排班,最后一行在详情页就可以看到了,这种情况下,添加『..全文』是成立的,所以就像图1.那样,当时他确实错误的,所以应该直接去判断最后一个字符是不是在富文本中,然后再判断如果加了"...全文",是不是可以符合宽度的要求

“...全文”的位置都是正确的
同一个cell,内部和外边比较,截取的都是一个特殊的字符串的尺寸
普通文本截取的时候也是正确合理的

项目缺点
第一.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
 */

修改后的样式
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • 基础命令 主要的命令和快捷键 Linux系统命令由三部分组成:cmd + [options]+[operation...
    485b1aca799e阅读 1,088评论 0 0
  • 周一学习会,学习“利他”这两字书中提到好多次,其实就是在提醒我们的大家要懂得感恩、懂得回馈社会,佛家的“因果报应”...
    周晨i阅读 226评论 0 0
  • 没有人真正的希望你过得好 这话不假 心中感慨万千 反过来想 确实如此 总之 过好自己的生活 不惹是非 过着耳根清净...
    优雅老太太阅读 176评论 0 0
  • 在不久前日本《读卖新闻》做的一项调查中显示,日本和中国分别有56%和44%不喜欢对方,但不喜欢并不能抹杀对方的成就...
    茸小呆阅读 1,205评论 0 14