美文网首页程序员iOS Developer
微博的角标"...全文"功能是如何实现的

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

作者: mkb2 | 来源:发表于2016-11-19 13:28 被阅读899次

    写在前面

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

    公司最近写了一个社交的功能模块,有以下几个功能
    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
     */
    
    
    修改后的样式

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/oonspttx.html