微博html怎么编辑器,制作一个微博文本编辑器
最近需要制作一個(gè)類似發(fā)微博的界面,支持“@用戶”以及“#話題#”等格式高亮,并且可輸入展示自定義表情圖片。期間寫了個(gè)demo,截圖如下:
demo.png
首先關(guān)于這種富文本展示就不造輪子了,直接使用了YYText ,可以省不少時(shí)間。
1 分析
首先效果大體效仿微博,"@用戶"與"#熱門話題#"并非綁定字符串,用戶可以隨時(shí)在其中插入編輯并高亮展示。
關(guān)于圖片樣式,這里隨便用了幾個(gè)表情作為示例,實(shí)現(xiàn)是利用圖片替換純文本中指定的字符串(比如“[大笑]” ),刪除時(shí)圖片整體刪除,復(fù)制時(shí)復(fù)制對(duì)應(yīng)的字符串,粘貼或者用戶打出對(duì)應(yīng)字符自動(dòng)轉(zhuǎn)換為表情圖片。
首先很容易想到用正則來匹配關(guān)鍵字符串,轉(zhuǎn)換為attribute string著色展示;利用NSTextAttachment插入圖片并替換字符。如果只是展示已經(jīng)足夠,但如果展示之后需要再次編輯,那么實(shí)現(xiàn)起來會(huì)很復(fù)雜(可以想到每增刪字符后都要重新匹配全文并重新賦予attribute string)。
其實(shí)在YYText的demo中實(shí)現(xiàn)的markdown語法解析,和本需求實(shí)現(xiàn)如出一轍,其實(shí)目的就是做一個(gè)“語法解析器” 。所以接下來參照YYTextSimpleMarkdownParser 自定義一個(gè)對(duì)象實(shí)現(xiàn)YYTextParser協(xié)議,并將對(duì)象復(fù)制給YYTextView的實(shí)例即可。
2 實(shí)現(xiàn)
首先創(chuàng)建自定義解析器并準(zhǔn)守協(xié)議
#import
#import "YYTextParser.h"
@interface TextParser : NSObject
@end
關(guān)于YYTextView的創(chuàng)建不再贅述,創(chuàng)建完成后賦值textParser屬性
self.textView.textParser = [[TextParser alloc]init];
接下來就該實(shí)現(xiàn)TextParser的.m文件了,查閱YYTextParser類發(fā)現(xiàn)只有一個(gè)require方法:
/**
When text is changed in YYTextView or YYLabel, this method will be called.
@param text The original attributed string. This method may parse the text and
change the text attributes or content.
@param selectedRange Current selected range in `text`.
This method should correct the range if the text content is changed. If there's
no selected range (such as YYLabel), this value is NULL.
@return If the 'text' is modified in this method, returns `YES`, otherwise returns `NO`.
*/
- (BOOL)parseText:(nullable NSMutableAttributedString *)text selectedRange:(nullable NSRangePointer)selectedRange;
這里注釋已比較清楚,需要配合YYTextView/YYLabel來使用,并會(huì)在text改變的時(shí)候調(diào)用,所以在這個(gè)方法里面對(duì)文本進(jìn)行解析再合適不過了。
首先在此之前,需要定義對(duì)應(yīng)的正則以及字體顏色等,這里說下正則這部分申明以及賦值:
NSRegularExpression *_regexAt;
NSRegularExpression *_regexPoundSign;
_regexAt = [NSRegularExpression regularExpressionWithPattern:@"@[\u4e00-\u9fa5a-zA-Z0-9_-]{2,30}" options:0 error:NULL];
_regexAt = [NSRegularExpression regularExpressionWithPattern:@"#[^#]+#" options:0 error:NULL];
這里的正則匹配了@之后2~30位長(zhǎng)度的字符,以及#之間的字符。
接下來單獨(dú)處理圖片相關(guān)的匹配
NSRegularExpression *_regexImage;
NSDictionary *_imageMapper;
這里的_imageMapper字典就是為了保存一份對(duì)應(yīng)關(guān)系,就是字符與圖片文件的映射,下面的代碼初始化字典,然后遍歷字典的key構(gòu)造匹配的正則字符串,最后賦值給_regexImage,需要注意的是iOS中需要對(duì)特殊字符進(jìn)行轉(zhuǎn)義。
_imageMapper = @{
@"[嘻嘻]" : [UIImage imageNamed:@"yb001"],
@"[呆]" : [UIImage imageNamed:@"yb002"],
@"[色]" : [UIImage imageNamed:@"yb003"]
};
NSMutableString *pattern = @"(".mutableCopy;
NSArray *allKeys = _imageMapper.allKeys;
NSCharacterSet *charset = [NSCharacterSet characterSetWithCharactersInString:@"$^?+*.,#|{}[]()\\"];
for (NSUInteger i = 0, max = allKeys.count; i < max; i++) {
NSMutableString *one = [allKeys[i] mutableCopy];
// escape regex characters
for (NSUInteger ci = 0, cmax = one.length; ci < cmax; ci++) {
unichar c = [one characterAtIndex:ci];
if ([charset characterIsMember:c]) {
[one insertString:@"\\" atIndex:ci];
ci++;
cmax++;
}
}
[pattern appendString:one];
if (i != max - 1) [pattern appendString:@"|"];
}
[pattern appendString:@")"];
_regexImage = [[NSRegularExpression alloc] initWithPattern:pattern options:kNilOptions error:nil];
剩下的就是協(xié)議方法的實(shí)現(xiàn)了
- (BOOL)parseText:(NSMutableAttributedString *)text selectedRange:(NSRangePointer)range {
__block BOOL changed = NO;
if (text.length == 0) { return NO; }
text.yy_font = _normalFont;
text.yy_color = _normalColor;
// @用戶
[_regexAt enumerateMatchesInString:text.string options:NSMatchingWithoutAnchoringBounds range:text.yy_rangeOfAll usingBlock:^(NSTextCheckingResult *result, NSMatchingFlags flags, BOOL *stop) {
NSRange range = result.range;
[text yy_setColor:_atTextColor range:range];
changed = YES;
}];
// #話題#
[_regexPoundSign enumerateMatchesInString:text.string options:NSMatchingWithoutAnchoringBounds range:text.yy_rangeOfAll usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) {
NSRange range = result.range;
[text yy_setColor:_atTextColor range:range];
changed = YES;
}];
// 圖片
if (_imageMapper.count){
NSArray *matches = [_regexImage matchesInString:text.string options:kNilOptions range:NSMakeRange(0, text.length)];
if (matches.count) {
NSRange selectedRange = range ? *range : NSMakeRange(0, 0);
NSUInteger cutLength = 0;
for (NSUInteger i = 0, max = matches.count; i < max; i++) {
NSTextCheckingResult *one = matches[i];
NSRange oneRange = one.range;
if (oneRange.length == 0) continue;
oneRange.location -= cutLength;
NSString *subStr = [text.string substringWithRange:oneRange];
UIImage *emoticon = _imageMapper[subStr];
if (!emoticon) continue;
CGFloat fontSize = kNormalFontSize;
CTFontRef font = (__bridge CTFontRef)([text yy_attribute:NSFontAttributeName atIndex:oneRange.location]);
if (font) fontSize = CTFontGetSize(font);
NSMutableAttributedString *atr = [NSAttributedString yy_attachmentStringWithEmojiImage:emoticon fontSize:fontSize];
[atr yy_setTextBackedString:[YYTextBackedString stringWithString:subStr] range:NSMakeRange(0, atr.length)];
[text replaceCharactersInRange:oneRange withString:atr.string];
[text yy_removeDiscontinuousAttributesInRange:NSMakeRange(oneRange.location, atr.length)];
[text addAttributes:atr.yy_attributes range:NSMakeRange(oneRange.location, atr.length)];
selectedRange = [self _replaceTextInRange:oneRange withLength:atr.length selectedRange:selectedRange];
cutLength += oneRange.length - 1;
}
if (range) *range = selectedRange;
changed = YES;
}
}
return changed;
}
上面有關(guān)圖片的代碼由于發(fā)生了文本替換會(huì)比較長(zhǎng),替換前后的range、光標(biāo)位置等內(nèi)容的重設(shè),需要把控細(xì)節(jié)有很多,可以下載demo來看(_replaceTextInRange方法就是重新設(shè)置選擇區(qū)域(光標(biāo)位置) 包含在demo中)。
3 其他細(xì)節(jié)
3.1 上面的圖片實(shí)現(xiàn):
NSMutableAttributedString *atr = [NSAttributedString yy_attachmentStringWithEmojiImage:emoticon fontSize:fontSize];
其實(shí)這代碼設(shè)置的是表情圖片,其尺寸是通過傳入的fontSize計(jì)算而成,倘若圖片并非整套表情而是一個(gè)長(zhǎng)寬不等的矩形圖片,則需要修改下這段代碼:
NSMutableAttributedString *atr = [NSAttributedString yy_attachmentStringWithContent:emoticon contentMode:UIViewContentModeCenter attachmentSize:emoticon.size alignToFont:_normalFont alignment:YYTextVerticalAlignmentCenter];
這樣就可以將不規(guī)則圖片插入文本中了。
3.2 關(guān)于YYTextBackedString 默認(rèn)情況下AttributedString中插入圖片然后log是沒有任何圖片的文本信息的,復(fù)制圖片后粘貼也是空字符串,但如果設(shè)置了YYTextBackedString就可以賦值文本屬性給圖片了。
[atr yy_setTextBackedString:[YYTextBackedString stringWithString:subStr] range:NSMakeRange(0, atr.length)];
3.3 本文為了盡量少貼不必要的代碼,去掉了一些判空判斷,字符串?dāng)?shù)組字典的值使用前盡量判斷是否為空。
3.4有關(guān)線程安全,本文用到的存有字符與圖片映射的字典_imageMapper由于非線程安全,在頻繁的編輯下是容易發(fā)生同時(shí)讀寫的情況,假如是固定的表情自初始化完畢后不會(huì)增刪就沒啥問題,但非這種情況就需要聲明”鎖“(NSLock, 信號(hào)量等控制均可) 來防止多線程同時(shí)訪問,并設(shè)置為默認(rèn)的atomic屬性,這里可以參照YYText中的YYTextParser.m文件有關(guān)實(shí)現(xiàn)。
4 To do
寫本文時(shí),其實(shí)還有一種格式需求沒有搞定,如下圖
image.png
微博里非常常見的一種格式,想簡(jiǎn)單些就是左邊圖片與“查看圖片”四個(gè)字共屬一圖,將圖片url復(fù)制給整張圖片,但會(huì)造成“查看圖片”四字無法折行的bug(其實(shí)算不算bug要看產(chǎn)品),接下來就是解決這個(gè)問題了。
Update 17-5-17 上面問題的解決
今天優(yōu)化這塊代碼,其中上面樣式也做了出來,如下圖:
image&textBinding.png
其中核心實(shí)現(xiàn)是將圖片與文字拼接成一個(gè)NSAttributedString,之后對(duì)拼接結(jié)果進(jìn)行binding與backed處理。
NSMutableAttributedString *atr = [NSMutableAttributedString yy_attachmentStringWithEmojiImage:emoticon fontSize:fontSize];
NSAttributedString *checkText = [[NSAttributedString alloc] initWithString:@"查看圖片"
attributes:@{
NSFontAttributeName: _normalFont,
NSForegroundColorAttributeName :_atTextColor }];
[atr appendAttributedString:checkText];
YYTextBinding *binding = [YYTextBinding bindingWithDeleteConfirm:YES];
[atr yy_setTextBinding:binding range:NSMakeRange(0, atr.length)];
[atr yy_setTextBackedString:[YYTextBackedString stringWithString:subStr] range:NSMakeRange(0, atr.length)];
[text replaceCharactersInRange:oneRange withAttributedString:atr];
這里還有一步,和之前只加圖片不同,當(dāng)協(xié)議方法調(diào)用并替換后,再次調(diào)用協(xié)議方法會(huì)發(fā)現(xiàn)傳入的text變成了\U0000fffc查看圖片 并非是賦值的YYTextBackedString,而在控制器打印textView.text沒問題,所以這里會(huì)造成“查看圖片”這四個(gè)字失去了設(shè)置的顏色,而被設(shè)置上了普通文字的顏色。
不過解決起來很簡(jiǎn)單,像匹配@標(biāo)記一樣增加一條正則:
_regexImageCheck = regexp("\U0000fffc查看圖片", 0);
然后再次匹配重設(shè)顏色即可。這里會(huì)有一個(gè)小bug,YYText會(huì)把圖片統(tǒng)一處理成\U0000fffc字符所以任意圖片后的“查看圖片”四字都會(huì)被重設(shè)高亮,目前還沒有更好的方案,未來再做優(yōu)化。
總結(jié)
以上是生活随笔為你收集整理的微博html怎么编辑器,制作一个微博文本编辑器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WEB API:语音识别
- 下一篇: 退出登录后点返回键 是登录状态_看了这5