您的位置:首页 > 其它

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

2015-08-07 23:36 459 查看
很多时候需要用到图文混排,例如聊天气泡中的表情,空间、微博中的表情,例如下图:



红心和文字在一起。

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

要实现这种功能,首先要介绍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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: