• (一一一)图文混排基础 -利用正则分割和拼接属性字符串


    很多时候需要用到图文混排,例如聊天气泡中的表情,空间、微博中的表情,例如下图:

    红心和文字在一起。

    比较复杂的情况是表情夹杂在文字之间。

    要实现这种功能,首先要介绍iOS中用于显示属性文字的类。

    用于文字显示的类除了text属性之外,还有attributedText属性,这个属性是NSAttributedString类型,通过这个属性可以实现不同文字的不同字体、颜色甚至把图片作为文字显示的功能。

    下面介绍这个字符串的使用。


    以一条微博内容为应用场景,介绍如何从中找出表情、话题等内容,其中表情替换成表情图片,话题等高亮显示。

    这里用到的内容主要有:正则表达式、NSAttributedString、NSTextAttachment等知识。

    【正则表达式】

    正则表达式在上一节(一一〇)正则表达式的基本使用与RegexKitLite的使用中有介绍,主要是为了找出所有特殊位置和非特殊位置。

    NSAttributedString

    这是一种能够对特定范围的文字设置属性、显示图片等功能。

    下面介绍通过普通字符串初始化NSAttributedString,并且把其中的表情([<表情名称>])、话题(#<话题内容>#)、URL全部高亮的方法。

    ①通过微博字符串text初始化一个属性字符串。

     NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] initWithString:text];
    ②指定匹配规则。

     NSString *emotionPattern = @"\[[a-zA-Z\u4e00-\u9fa5]+\]";
     NSString *topicPattern = @"#[0-9a-zA-Z\u4e00-\u9fa5]+#";
     NSString *urlPattern = @"[a-zA-z]+://[^\s]*";
     NSString *pattern = [NSString stringWithFormat:@"%@|%@|%@",emotionPattern,topicPattern,urlPattern];
    ③对匹配到的范围进行高亮,只需要调用NSMutableAttributedString的addAttribute:::属性对特定范围的文字设置颜色属性。

     [text enumerateStringsMatchedByRegex:pattern usingBlock:^(NSInteger captureCount, NSString *const __unsafe_unretained *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
            
       [attributedText addAttribute:NSForegroundColorAttributeName value:[UIColor blueColor] range:*capturedRanges];
            
     }];

    通过这样的方法,所有匹配到的范围都会被标红。

    ④设置控件的attributedText属性为上面创建的attributedText值即可。


    NSTextAttachment

    NSTextAttachment可以设置image和bounds来指定图片和尺寸,并且可以包装成NSAttributedString,这是图文混排的基础。

    下面介绍创建一个包含小图片的NSTextAttachment,并且用NSAttributedString包装的方法。

    调整bounds的x、y可以修正图片在字符串中的位置。

     NSTextAttachment *attach = [[NSTextAttachment alloc] init];
     attach.image = [UIImage imageNamed:@"avatar_vgirl"];
     attach.bounds = CGRectMake(0, -3, 15, 15);
     NSAttributedString *emotionStr = [NSAttributedString attributedStringWithAttachment:attach];
    

    【将字符串中的表情部分替换为表情图片】

    为了简单,把所有表情位置的内容都替换为一张图片avatar_vgirl。

    有一个自然的思路是调用attributedText的replace方法,把表情替换成图片(NSAttributedString包装的NSTextAttachment)。但是这个方法是有问题的。

    例如下面的句子:

    今天发送了[笑cry]一件很有意思的事情[笑cry]。

    替换第一个表情[笑cry]为图片时,字符串的长度可能会发生变化,此时再处理第二个表情时计算出的位置可能就是错误的,因此应该先找到所有的表情位置然后统一替换。

    因此我们可以用RegexKitLite的两个方法,分别找出匹配的和未匹配的,把他们存起来,然后按照位置的先后排序,最后按顺序拼接,拼接时对于表情换成图片,其他特殊字符高亮,余下的正常显示。

    因为我们要把文字和范围全部放入数组,因此应该定义一个模型,为了方便起见,设置两个成员用于存储当前部分是不是表情、是不是特殊内容。

    模型的代码如下:

    @interface TextSegment : NSObject
    
    @property (nonatomic, copy) NSString *text;
    @property (nonatomic, assign) NSRange range;
    @property (nonatomic, assign, getter=isSpecial) BOOL special;
    @property (nonatomic, assign, getter=isEmotion) BOOL emotion;
    
    @end

    假设text是微博的全部内容,下面的代码实现把特殊内容和非特殊内容全部放入数组,并且判断是否是表情,表情的特点是有[ ],利用hasPrefix:和hasSufix:判断。

        NSString *emotionPattern = @"\[[a-zA-Z\u4e00-\u9fa5]+\]";
        NSString *topicPattern = @"#[0-9a-zA-Z\u4e00-\u9fa5]+#";
        NSString *urlPattern = @"[a-zA-z]+://[^\s]*";
        NSString *pattern = [NSString stringWithFormat:@"%@|%@|%@",emotionPattern,topicPattern,urlPattern];
        
        // 把[表情]替换成attachment图片,不能用replace和insert,因为会改变后面的相对位置,应该先拿到所有位置,最后再统一修改。
        // 应该打散特殊部分和非特殊部分,然后拼接。
        NSMutableArray *parts = [NSMutableArray array];
        [text enumerateStringsMatchedByRegex:pattern usingBlock:^(NSInteger captureCount, NSString *const __unsafe_unretained *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
            
            if ((*capturedRanges).length == 0) return;
            
            TextSegment *seg = [[TextSegment alloc] init];
            seg.text = *capturedStrings;
            seg.range = *capturedRanges;
            seg.special = YES;
            
            seg.emotion = [seg.text hasPrefix:@"["] && [seg.text hasSuffix:@"]"];
            
            [parts addObject:seg];
            
        }];
        
        [text enumerateStringsSeparatedByRegex:pattern usingBlock:^(NSInteger captureCount, NSString *const __unsafe_unretained *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
            
            if ((*capturedRanges).length == 0) return;
           
            TextSegment *seg = [[TextSegment alloc] init];
            seg.text = *capturedStrings;
            seg.range = *capturedRanges;
            seg.special = NO;
            [parts addObject:seg];
            
        }];
    通过上面的代码,我们把所有的文字部分都放入了parts数组中,为了拼接方便,我们应该按照位置的起始排序,从前到后依次拼接。

    这就需要对parts数组依据模型的range.location属性排序,比较常用的是根据block排序。

    block传入两个数组中的对象obj1、obj2,要求返回排序规则NSOrderedAscending、NSOrderedSame、NSOrderedDescending。

    NSOrderedAscending指的是obj1<obj2,系统默认按照升序排序,因此为了实现升序,发现obj1<obj2应该返回NSOrderedAscending。

     [parts sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
            
            TextSegment *ts1 = obj1;
            TextSegment *ts2 = obj2;
            
            // Descending指的是obj1>obj2
            // Ascending指的是obj1<obj2
            // 要实现升序,按照上面的规则返回。
    
            // 系统默认按照升序排列。
            if (ts1.range.location < ts2.range.location) {
                return NSOrderedAscending;
            }
            
            return NSOrderedDescending;
            
     }];

    接下来只需要从前到后拼接一个新创建的NSAttributedString,根据内容的不同拼接不同的内容。

     NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] init];
     NSInteger cnt = parts.count;
     for (NSInteger i = 0; i < cnt; i++) {
          TextSegment *ts = parts[i];
          if (ts.isEmotion) {
              NSTextAttachment *attach = [[NSTextAttachment alloc] init];
              attach.image = [UIImage imageNamed:@"avatar_vgirl"];
              attach.bounds = CGRectMake(0, -3, 15, 15);
              NSAttributedString *emotionStr = [NSAttributedString attributedStringWithAttachment:attach];
              [attributedText appendAttributedString:emotionStr];
          }else if(ts.isSpecial){
              NSAttributedString *special = [[NSAttributedString alloc] initWithString:ts.text attributes:@{NSForegroundColorAttributeName:[UIColor redColor]}];
              [attributedText appendAttributedString:special];
          }else{
              [attributedText appendAttributedString:[[NSAttributedString alloc] initWithString:ts.text]];
          }
     }
    最后把这个attributedText设置到控件上。


    【计算NSAttributedString的尺寸】

    ①之前的text,计算尺寸的代码如下:需要指定字体和范围限制,一般是限制宽度,高度不限制(设置为MAXFLOAT),这样可以计算出正确的多行尺寸。

     CGSize contentSize = [text sizeWithFont:ContentFont constrainedToSize:CGSizeMake(MaxW, MaxH)]
    ②对于NSAttributedString,也有方法用于计算尺寸,注意计算之前必须为attributedText设定字体

     [attributedText addAttribute:NSFontAttributeName value:[UIFont systemFontOfSize:14] range:NSMakeRange(0, attributedText.length)];
    然后调用boundingRectWithSize:::计算尺寸,注意options必须选择options:NSStringDrawingUsesLineFragmentOrigin才能得到正确尺寸。

     CGSize contentSize = [attributedText boundingRectWithSize:CGSizeMake(maxWidth, maxHeight) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;

  • 相关阅读:
    20180320作业2:进行代码复审训练
    20180320作业1:源代码管理工具调查
    软工作业PSP与单元测试练习
    软工课后作业01-P18第四题
    20180320作业2:进行代码复审训练
    判断传入的电子邮箱账号的正确性
    软工课后作业01-00365
    实现模块判断传入的电子邮箱账号的正确性
    个人介绍
    20180320作业2:进行代码复审训练
  • 原文地址:https://www.cnblogs.com/aiwz/p/6154073.html
Copyright © 2020-2023  润新知