zoukankan      html  css  js  c++  java
  • iOS 图文混排

    使用系统自带的NSAttributedString来处理,对于一般的图文混排已经足够了,但是,有一个缺点就是NSAttributedString并不支持gif动画。实际上,使用gif动画还是挺卡的。

    思路:

    1.通过RegexKitLite 正则,匹配出所有需要特殊处理的字符

    2.由于表情图片占用一个字符,使用直接替换范围的方式,会导致后面的表情范围不对。有两种处理方案

    方案一: 

    • 使用两个数组,分别装特殊字符(文字内容,文字范围,是否为特殊字符,是否为表情)和非特殊字符,按范围排序成一个新数组
    • 循环新数组List,通过判断是否为表情和是否为特殊字符来添加appendAttributedString属性。

    方案二:

    通过递归的方式,每找到一个表情,先替换掉,再递归找下一个

    当前,使用的是方案一,核心代码:

    /**
     *  普通文字 --> 属性文字
     *
     *  @param text 普通文字
     *
     *  @return 属性文字
     */
    - (NSAttributedString *)attributedTextWithText:(NSString *)text
    {
        NSMutableAttributedString *attributedText = [[NSMutableAttributedString alloc] init];
        
        // 表情的规则
        NSString *emotionPattern = @"\[[0-9a-zA-Z\u4e00-\u9fa5]+\]";
        // @的规则
        NSString *atPattern = @"@[0-9a-zA-Z\u4e00-\u9fa5-_]+";
        // #话题#的规则
        NSString *topicPattern = @"#[0-9a-zA-Z\u4e00-\u9fa5]+#";
        // url链接的规则
        NSString *urlPattern = @"\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))";
        NSString *pattern = [NSString stringWithFormat:@"%@|%@|%@|%@", emotionPattern, atPattern, topicPattern, urlPattern];
        
        // 遍历所有的特殊字符串
        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;
            
            TextPartModel *part = [[TextPartModel alloc] init];
            part.special = YES;
            part.text = *capturedStrings;
            part.emotion = [part.text hasPrefix:@"["] && [part.text hasSuffix:@"]"];
            part.range = *capturedRanges;
            [parts addObject:part];
        }];
        
        // 遍历所有的非特殊字符
        [text enumerateStringsSeparatedByRegex:pattern usingBlock:^(NSInteger captureCount, NSString *const __unsafe_unretained *capturedStrings, const NSRange *capturedRanges, volatile BOOL *const stop) {
            if ((*capturedRanges).length == 0) return;
            
            TextPartModel *part = [[TextPartModel alloc] init];
            part.text = *capturedStrings;
            part.range = *capturedRanges;
            [parts addObject:part];
        }];
        
        // 排序
        // 系统是按照从小 -> 大的顺序排列对象
        [parts sortUsingComparator:^NSComparisonResult(TextPartModel *part1, TextPartModel *part2) {
            // NSOrderedAscending = -1L, NSOrderedSame, NSOrderedDescending
            // 返回NSOrderedSame:两个一样大
            // NSOrderedAscending(升序):part2>part1
            // NSOrderedDescending(降序):part1>part2
            if (part1.range.location > part2.range.location) {
                // part1>part2
                // part1放后面, part2放前面
                return NSOrderedDescending;
            }
            // part1<part2
            // part1放前面, part2放后面
            return NSOrderedAscending;
        }];
        
        UIFont *font = [UIFont systemFontOfSize:15];
        NSMutableArray *specials = [NSMutableArray array];
        // 按顺序拼接每一段文字
        for (TextPartModel *part in parts) {
            // 等会需要拼接的子串
            NSAttributedString *substr = nil;
            if (part.isEmotion) { // 表情
                NSTextAttachment *attch = [[NSTextAttachment alloc] init];
                NSString *name = [EmoticonTool emoticonWithChs:part.text].png;
                if (name) { // 能找到对应的图片
                    attch.bounds = CGRectMake(0, -3, font.lineHeight, font.lineHeight);
                    attch.image = [UIImage imageNamed:name];
                    substr = [NSAttributedString attributedStringWithAttachment:attch];
                } else { // 表情图片不存在
                    substr = [[NSAttributedString alloc] initWithString:part.text];
                }
            } else if (part.special) { // 非表情的特殊文字
                substr =[[NSAttributedString alloc] initWithString:part.text attributes:@{NSForegroundColorAttributeName : [UIColor redColor]
                                                                                          }];
                SpecialModel *s = [[SpecialModel alloc] init];
                s.text = part.text;
                NSUInteger loc = attributedText.length;
                NSUInteger len = part.text.length;
                s.range = NSMakeRange(loc, len);
                [specials addObject:s];
            } else { // 非特殊文字
                substr = [[NSAttributedString alloc] initWithString:part.text];
            }
            [attributedText appendAttributedString:substr];
        }
        
        // 一定要设置字体,保证计算出来的尺寸是正确的
        [attributedText addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, attributedText.length)];
        [attributedText addAttribute:@"specials" value:specials range:NSMakeRange(0, 1)];
        
        return attributedText;
    }
    View Code

     在使用过程中,我们需要注意一点,计算文本的宽高,通过下面来计算

     [status.attributedText boundingRectWithSize:CGSizeMake(maxW, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:nil].size;

    在计算过程中,我们并没有传字体大小,因而,我们需要给attributedText一开始设定字体大小:

    // 一定要设置字体,保证计算出来的尺寸是正确的
        [attributedText addAttribute:NSFontAttributeName value:font range:NSMakeRange(0, attributedText.length)];

    设置行高

        // 定义行高
        NSMutableParagraphStyle * paragraphStyle = [[NSMutableParagraphStyle alloc] init];
        [paragraphStyle setLineSpacing:5];
        [attributedText addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, attributedText.length)];

    附源代码:http://pan.baidu.com/s/1o8Su8H0

    目录说明:

    HomeViewController  ---列表

    StatusCell       ---列表TableCell

    StatusFrame       ---列表TableCell高度。由于cell的高度是不固定的,因此我们定义StatusFrame来管理所有的控件尺寸,最后返回总高度

    StatusModel      ---微博模型,图文混排处理,在这里做核心混排处理,通过添加attributedText属性处理。

      --TextPartModel  --StatusModel嵌套属性,用于记录RegexKitLite 正则匹配出的字符

      --SpecialModel   --StatusModel嵌套属性,用于特殊实符点击变色的范围比比较

      --UserModel     --StatusModel嵌套属性,用户模型

  • 相关阅读:
    容器跨主机网络通信学习笔记(以Flannel为例)
    Kubernetes控制器Job和CronJob
    记一次使用Flannel插件排错历程
    Kubernetes控制器Deployment
    Kubernetes如何通过StatefulSet支持有状态应用?
    react18 来了,我 get 到...
    gojs 实用高级用法
    vuecli3 vue2 保留 webpack 支持 vite 成功实践
    calibre 报错 This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem. 解决
    unable to recognize "*.yaml": no matches for kind "RoleBinding" in version "rbac.authorization.k8s.io/v1beta1"
  • 原文地址:https://www.cnblogs.com/jys509/p/5539217.html
Copyright © 2011-2022 走看看