程序员iOS Developer

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

2016-11-19  本文已影响899人  mkb2

写在前面

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

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

修改后的样式
上一篇 下一篇

猜你喜欢

热点阅读