最近因为项目原因,需要完成类似微博的@功能,用了一下YYTextView。
注意点: UIViewContoller直接使用YYTextView是会报错的,提示:exception:Application windows are expected to have a root view controller at the end of application launch
解决方案:必须要套一个UINavigationController:,然后把设置window的rootViewController是UINavigationController:
```
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];//设置窗口
ViewController *mvc = [[ViewController alloc] init];
```
//注意看这里:
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:mvc];
nav.navigationBarHidden = YES;//是否隐藏导航栏
self.window.rootViewController = nav;//进入的首个页面
[self.window makeKeyAndVisible];//显示
Demo地址:https://github.com/zhangkang317/YYTextView-Demon
微博的实现原理:服务端根据用户发布的微博内容, 正则匹配出需要@的用户的名字列表,然后根据名字去查用户的ID
我的实现原理:如果按照微博的实现方式,会对我们后台的服务压力比较大,所以自己想了一下就参考了邮箱的方式实现,用户编写内容时,通过选择的方式添加@用户,并以图片的方式展示@用户,同时把用户的信息带上,在发布的时候遍历文本内容,取出图片对象的用户信息,做下简单的数据处理,然后返回给服务端。一下是我的代码具体实现方式。
1、创建YYTextView,
- (YYTextView *)textView {
if(_textView ==nil) {
_textView = [[YYTextView alloc] initWithFrame:CGRectMake(10,10, UI_SCREEN_WIDTH -20,90)];
_textView.textColor = [UIColor blackColor];
_textView.font = [UIFont systemFontOfSize:15*SP_SCREEN];
_textView.scrollEnabled =NO;
_textView.textParser = [[LPPZSendContentTextParser alloc] init];
_textView.delegate =self;
_textView.inputAccessoryView = [UIView new];
@weakify(self);
}
return_textView;
}
2、将选择的用户数据拼接到文本的内容
vc.didSelectUserBlock =^(SnsUser*user) {
@strongify(self);
[self.textView deleteBackward];
//将用户的名字user.nickName,用户的ID(user.id)以特定的格式传给内容,方便后面的内容匹配,传入的格式例如:[@张三 -86634993]:
重点:‘-’前面一定要接一个空格,方便后面发布成功后查看文章时将@用户格式区别开来
[self.textView replaceRange:self.textView.selectedTextRange
withText:[NSStringstringWithFormat:@"[@%@ -%@]",user.nickName,user.id]];
};}
3、每当YYText调用- (void)replaceRange:(UITextRange*)range withText:(NSString*)text;都会调用这个方法LPPZSendContentTextParser这个类的方式
- (BOOL)parseText:(NSMutableAttributedString*)text selectedRange:(NSRangePointer)selectedRange ;在这个方法里我们会对文本的内容做处理
- (BOOL)parseText:(NSMutableAttributedString*)text selectedRange:(NSRangePointer)selectedRange {
{
//1、正则匹配出符合[@张三 -86634993]这种格式的内容,正则表达式为(\\[[^\\]]*\\])
NSArray *emoticonResults = [[LPPZHelper regex_MoodAtUser] matchesInString:text.string options:kNilOptions range:text.yy_rangeOfAll];
NSUIntegerclipLength =0;
for(NSTextCheckingResult*emoinemoticonResults) {
if(emo.range.location==NSNotFound&& emo.range.length<=1)continue;
NSRangerange = emo.range;
range.location-= clipLength;
if([textyy_attribute:YYTextAttachmentAttributeName atIndex:range.location])continue;
//2.根据range取出[@张三 -86634993]
NSMutableString *tmp1 = [NSMutableString stringWithString: [text.string substringWithRange:range]];
//3、去掉头部和尾部的中括号[],得到@张三 -86634993
NSMutableString *tmp2 = [NSMutableString stringWithString: [tmp1 stringByReplacingOccurrencesOfString:@"[" withString:@""]];
NSMutableString *tmp3 = [NSMutableString stringWithString: [tmp2 stringByReplacingOccurrencesOfString:@"]" withString:@""]];
//4.得到"@张三 "(不包含双引号)
NSString *emoString = [tmp3 componentsSeparatedByString:@"-"].firstObject;
//4.得到用户Id"86634993"(不包含双引号)
NSString *userId = [tmp3 componentsSeparatedByString:@"-"].lastObject;
//5、做一些异常数据的场景处理
if(![tmp1hasPrefix:@"[@"]) {
continue;
}
if ([tmp3 componentsSeparatedByString:@"-"].count !=2) {
continue;
}
if(NULLString(userId)) {
continue;
}
//6、根据"@张三 "(不包含双引号)生成一个对象LPPZLabelImage
LPPZLabelImage * image = [LPPZLabelImage imageWithText:emoString
font:_atUserFont
textColor:HEXRGBCOLOR(0x0068b7)
backgroundColor:[UIColor whiteColor]];
if(!image.image)continue;
__blockBOOLcontainsBindingRange =NO;
[text enumerateAttribute:YYTextBindingAttributeName
inRange:range
options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired
usingBlock:^(idvalue, NSRange range,BOOL*stop) {
if(value) {
containsBindingRange =YES;
*stop =YES;
}
}];
if(containsBindingRange)continue;
YYTextBackedString *backed = [YYTextBackedString stringWithString:emoString];
NSMutableAttributedString*atr = [[NSMutableAttributedString alloc] initWithString:YYTextAttachmentToken];
YYTextAttachment *attach = [YYTextAttachment new];
//7、LPPZLabelImage的image赋值给YYTextAttachment
attach.content = image.image;
attach.contentMode = UIViewContentModeScaleAspectFit;
//8、YYTextAttachment对象包含了用户昵称和用户Id
attach.userInfo =@{kLPPZLinkATUserID:userId ,kLPPZLinkATUserName :emoString};
[atr yy_setTextAttachment:attach range:NSMakeRange(0, atr.length)];
//9.设置YYTextAttachment的大小以及位置
YYTextRunDelegate *delegate = [YYTextRunDelegate new];
delegate.width = image.lppz_TextSize.width;
CGFloatfontHeight = _font.ascender - _font.descender;
CGFloatyOffset = _font.ascender - fontHeight *0.5;
delegate.ascent = image.lppz_TextSize.height *0.5+ yOffset;
delegate.descent = image.lppz_TextSize.height - delegate.ascent;
if(delegate.descent <0) {
delegate.descent =0;
delegate.ascent = image.lppz_TextSize.height;
}
CTRunDelegateRef delegateRef = delegate.CTRunDelegate;
[atr yy_setRunDelegate:delegateRef range:NSMakeRange(0, atr.length)];
if(delegate) CFRelease(delegateRef);
//10,此时图片富文本对应的字符串内容是"@张三 "(不包含双引号) ,(传给服务端的内容是字符串而不是富文本)
[atr yy_setTextBackedString:backed range:NSMakeRange(0, atr.length)];
[atr yy_setTextBinding:[YYTextBinding bindingWithDeleteConfirm:NO] range:NSMakeRange(0, atr.length)];
//11.textView原来位置的内容是[@张三 -86634993] 现在替换成了YYTextAttachment(图片富文本)
[textreplaceCharactersInRange:range withAttributedString:atr];
if(selectedRange) {
*selectedRange = [self_replaceTextInRange:range withLength:atr.length selectedRange:*selectedRange];
}
clipLength += range.length- atr.length;
}
}
return YES;
}
然后这个时候YYtextView展示的内容不是text 而是富文本NSMutableAttributedString
4、用户点击提交的时候对文本内容我们将遍历内容
//在第三步,我们放的是YYTextAttachment对象,遍历的时候会取出这部分内容,而这部分内容是@用户的图片富文本,
[self.textView.attributedText enumerateAttribute:YYTextAttachmentAttributeName
inRange:NSMakeRange(0,self.textView.attributedText.string.length)
options:0
usingBlock:^(idvalue,NSRangerange,BOOL*stop) {
if([valueisKindOfClass: [YYTextAttachmentclass]]) {
YYTextAttachment* attachMent = (YYTextAttachment*)value;
//取出的图片富文本中包含了之前存进入的 用户数据 if(attachMent.userInfo!=nil) {
[self.viewModeladdAtUserInfo:attachMent.userInfo];
}
}
}];
遍历完毕取出需要的用户数据后,就可以单独发送给服务端,