我们每个人的手机都安装有微信App,肯定大家都使用过以下场景在聊天群中,当有新的成员进入的时候,可以看到 某某邀请了某某1进入群聊,或者当你是群主的时候,某管理人员邀请了几个人进入时候看到 某某管理员邀请了张三、李四,王五,张三(同名)进入了群聊。对于邀请人员或者被邀请人员,我们发现人员的名称是可以点击的,即使同名的人,点击其名字也可以跳转到各自的主页详情中。
如何存储人员的id?
一条群成员变更通知就是一串文本,我们点击的时候肯定要存储好他的人员id,然后点击的时候就能拿到对应人员的id。从最简单的存储方案来看,这条内容可以写成以下格式: 管理员刘某某邀请了张三、李四、王五、张三进入了群聊。换成代码的格式即为:“管理员\”刘某某::1000\”邀请了\”张三::1001、李四::1002、王五::1003、张三::1004\”进入了群聊”。其中的代码格式为userName::userId这样一种形式进行拼接,中间的符号可以采用其他特殊符号代替。这里为了区别用户特殊性输入用了双冒号。多个成员之间用顿号隔开,人员与普通内容之间用符号双引号\”进行分割(斜杆为转义符)。 我们需要对这一串文字表达式进行解析进行UI显示,把人员名字后的拼接符号用户id进行过滤掉,组合成不带用户id的易懂文字内容给到用户。
解析带用户id的字符串规则,拼装成正常文本
判断待解析的目标内容是否包含我们定义的特殊连接符号与双引号,符合条件的情况下第一步以双引号将文本进行分割。分为以下步骤。
- 双引号将文本进行分割成不同段的数组。
- 遍历数组,判断不同段中是否还有顿号,如果有则需要再根据顿号进行分割成最小粒化段(userName::userId),否则直接判断是否最小粒化段(判断是否包含特殊符::),如果包含,则以特舒符再将文本分割、否则直接添加到可变字符串中进行拼接。这样我们就能够拿到userName 以及 userId. 另外准备一个数组,存储每一个被分割的最小粒化段 {userName::userId}。
- 记得把人员之间的顿号,以及普通文本与人员之间的双引号加回去。最终的到了正常显示的文本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| if([msgBody containsString:@"\""] && [msgBody containsString:@"::"]){ NSArray *descArr = [msgBody componentsSeparatedByString:@"\""]; NSMutableString *afterFilterIdStr = [[NSMutableString alloc] init]; //过滤出Id后要显示在界面的str NSMutableArray *filterEmpDicArr = [[NSMutableArray alloc] init]; //过滤出成员
NSInteger findIndex = 0; for (NSString *eachStr in descArr) { findIndex ++; //加入双引号 if(eachStr.length > 0){ if([eachStr containsString:@"、"]){ NSArray *namesArr = [eachStr componentsSeparatedByString:@"、"]; NSInteger otherFindIndex = 0; for(NSString *otherName in namesArr){ otherFindIndex ++; if([otherName containsString:@"::"]){ //需分割取得名字 NSArray *itemStrArr = [otherName componentsSeparatedByString:@"::"]; if(itemStrArr.count){ NSString *empName = itemStrArr.firstObject; NSString *empId = itemStrArr.lastObject; [afterFilterIdStr appendString:empName]; [filterEmpDicArr addObject:@{@"name":empName,@"id":empId}]; } } if(otherFindIndex < namesArr.count){ [afterFilterIdStr appendString:@"、"]; } }
}else{ if([eachStr containsString:@"::"]){ //需分割取得名字 NSArray *itemStrArr = [eachStr componentsSeparatedByString:@"::"]; if(itemStrArr.count){ NSString *empName = itemStrArr.firstObject; NSString *empId = itemStrArr.lastObject; [afterFilterIdStr appendString:empName];
NSString * oldContainsStr = @"将"; //兼容移除群聊的显示结果 if([empId containsString:oldContainsStr]){ [afterFilterIdStr appendString:oldContainsStr]; NSArray *oldArr = [empId componentsSeparatedByString:oldContainsStr]; empId = oldArr.firstObject; }
[filterEmpDicArr addObject:@{@"name":empName,@"id":empId}]; } }else{ //普通字符串 [afterFilterIdStr appendString:eachStr]; } } } if(findIndex < descArr.count){ [afterFilterIdStr appendString:@"\""]; } } NSString *realMsgBody = [afterFilterIdStr copy]; }
|
通过以上代码,我们得到realMsgBody就是需要在文本上直接显示的内容,另外我们还得到了filterEmpDicArr包含了成员id的一个map。
渲染人员名字、增加样式蓝色,表示可以点击
由上一步,我们得到可正常显示在UI上的文字,另外还得到一个人名id的字典数组。渲染显示我们采用YYLabel设置Attribute的方式。先设置全内容属性mAttributedString。然后循环遍历filterEmpDicArr,根据人名创建名字高亮的lightAttribute。根据人名得到nameRange, 最后再采用替换方式将对应range进行替换掉属性。代码:
1
| [mAttributedString replaceCharactersInRange:nameRange withAttributedString:lightNameAttributeStr];
|
这里有一个问题,当匹配到第四个人时候,取到的nameRange是第一个人张三,结果并不对。来看看我们取nameRange的方式:
1
| NSRange range = [realMsgBody rangeOfString:empName]; //可能存在多个
|
这个时候就需要判断名字是否在文本中多次出现。当同名时候,我们需要把匹配到该人名的range全部找出来,而不是一个range。 当遍历当前的name时候,检查该人名出现的次数,出现第几次则这个当前遍历的人名的range则是对应第几个range. 这个逻辑才是正确的!这个时候,我们可以把当前遍历的userId对应的正确的range记录下来存到一个map中。最后当用户点击蓝色某名字时候label点击处所对应的range会返回,这个时候我们就可以根据range来命中userId了。当点击同名的时候不知道点击的是谁的问题就可以迎刃而解!此处应该有点赞,哈。过奖了,只是一个技巧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| NSString *realMsgBody = [afterFilterIdStr copy]; NSMutableAttributedString *mAttributedString = [[NSMutableAttributedString alloc] initWithString:realMsgBody]; mAttributedString.yy_font = labelFont; mAttributedString.yy_color = message_centerTextColor; mAttributedString.yy_alignment = NSTextAlignmentCenter; NSMutableDictionary *recordNameCountDic = [[NSMutableDictionary alloc] init]; //缓存每个名字记录的次数 NSMutableDictionary *recordRangeEmpIdDic = [[NSMutableDictionary alloc] init]; //记录每个名字的range对应的id //循环找出名字,并且替换attribute; for (NSDictionary *empDic in filterEmpDicArr) { NSString *empName = empDic[@"name"]; NSString *empId = empDic[@"id"]; NSMutableAttributedString *lightNameAttributeStr = [[NSMutableAttributedString alloc] initWithString:empName]; lightNameAttributeStr.yy_font = labelFont; lightNameAttributeStr.yy_underlineStyle = NSUnderlineStyleNone; //NSUnderlineStyleSingle; lightNameAttributeStr.yy_color = message_centerTextColor; lightNameAttributeStr.yy_lineSpacing = 2;
__weak typeof(self) weakSelf = self; [lightNameAttributeStr yy_setTextHighlightRange:lightNameAttributeStr.yy_rangeOfAll color:[UIColor colorWithRed:110/255.0f green:121/255.0f blue:151/255.0f alpha:1] backgroundColor:[UIColor colorWithWhite:0.000 alpha:0.0] tapAction:^(UIView *containerView, NSAttributedString *text, NSRange range, CGRect rect){ //点击名字跳转 // NSString *empName = [text.string substringWithRange:range]; NSString *empId = [recordRangeEmpIdDic objectForKey:[NSValue valueWithRange:range]]; if(empId && weakSelf.tappedNameBlock){ weakSelf.tappedNameBlock(empId); } }];
NSRange range = [realMsgBody rangeOfString:empName]; //可能存在多个 //同名可匹配出多个range NSArray *rangeArr = [self rangeOfSubString:empName inString:realMsgBody]; if(rangeArr.count && rangeArr.count > 1){
//有多个重名,拿字典记录的值{name:count} 名字次数. count有存切大于1时,本次的range为 rangeArr【count】 if([recordNameCountDic objectForKey:empName]){ NSInteger nameCount = [[recordNameCountDic objectForKey:empName] integerValue]; nameCount++; //rangeArr[name]记录的count累加 [recordNameCountDic setObject:@(nameCount) forKey:empName]; NSValue * value = rangeArr[nameCount-1]; range = [value rangeValue]; }else{ [recordNameCountDic setObject:@1 forKey:empName]; NSValue * value = rangeArr.firstObject; range = [value rangeValue]; } }
//根据range来记录id NSValue *rangeKey = [NSValue valueWithRange:range]; [recordRangeEmpIdDic setObject:empId forKey:rangeKey];
[mAttributedString replaceCharactersInRange:range withAttributedString:lightNameAttributeStr]; } mAttributedString.yy_lineSpacing = 2; self.groupInfolabel.attributedText = mAttributedString;
|
以下是匹配一个字符串里包含字段某字符串的所有range
1 2 3 4 5 6 7 8 9 10 11 12 13
| - (NSArray*)rangeOfSubString:(NSString*)subStr inString:(NSString*)string { NSMutableArray *rangeArray = [NSMutableArray array]; NSString*string1 = [string stringByAppendingString:subStr]; NSString *temp; for(int i =0; i < string.length; i ++) { temp = [string1 substringWithRange:NSMakeRange(i, subStr.length)]; if ([temp isEqualToString:subStr]) { NSRange range = {i,subStr.length}; [rangeArray addObject: [NSValue valueWithRange:range]]; } } return rangeArray; }
|
正则过滤处理
将符合改正则表达式的字符串找出来,NSRegularExpression可以拿到NSTextCheckingResult,然后result可以拿到range.把对应的字符串截取出来。最后再循环遍替换成空字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| if([msgBody containsString:@"::"]){ //匹配替换掉::工号显示。 NSMutableArray *replaceFormatStrArr = [[NSMutableArray alloc] init]; NSError *error = nil; NSRegularExpression *regexp = [NSRegularExpression regularExpressionWithPattern:@"::\\d{1,}" options:NSRegularExpressionCaseInsensitive error:&error]; [regexp enumerateMatchesInString:msgBody options:0 range:NSMakeRange(0, msgBody.length) usingBlock:^(NSTextCheckingResult *result, __unused NSMatchingFlags flags, __unused BOOL *stop) { if (flags != NSMatchingInternalError) { NSRange firstHalfRange = [result rangeAtIndex:0]; if (firstHalfRange.length > 0) { NSString *resultString1 = [msgBody substringWithRange:result.range]; [replaceFormatStrArr addObject:resultString1]; NSLog(@"result1 = %@",resultString1); } } }];
for(NSString *formatStr in replaceFormatStrArr){ msgBody = [msgBody stringByReplacingOccurrencesOfString:formatStr withString:@""]; } }
|