DTCoreText使用总结

总结在项目中使用DTCoreText时,遇到一些问题和解决方法。

安装运行

GitHub 地址: https://github.com/Cocoanetics/DTCoreText

官方文档: https://docs.cocoanetics.com/DTCoreText

直接下载下的DTCoreText是不能直接运行的,会报一些头文件找不到的错’import <DTFoundation/DTWeakSupport.h> file not found.’原因是DTCoreText依赖第DTFoundation这个库,所以需要执行一些操作安装DTFoundation库。

在本地新建一个文件DTCoreText,打开终端进入这个这个文件下
git clone git@github.com:Cocoanetics/DTCoreText.git

git submodule init

git submodule update

然后运行DTCoreText.xcodeproj,在使用的时候建议使用pod 'DTCoreText 来安装。

直接在终端上执行:
git clone --recursive https://github.com/Cocoanetics/DTCoreText.git Externals/DTCoreText

安装文档:https://docs.cocoanetics.com/DTCoreText/docs/Setup%20Guide.html

显示文本

NSString *html = @"<p>这是一段使用<strong>DTCoreText</strong>显示的富文本</p><p>这是第二段使用<span style=\"text-decoration:underline;\">DTCoreText</span>显示的富文本</p>";

现在我们拿到一段html字符串,转换成NSAttributedString对象,然后使用DTCoreText的DTAttributedLabel进行显示。
DTCoreText 目前没有设置段间距的属性,只能丢到源码里,然后去解析这些属性,NSString *tag = @”“; 放到前面,设置段间距。这里涉及到一些html的东西。

- (NSAttributedString *)configAttributedString:(NSString *)html
{
NSString *tag = @"<style>p{margin:2 auto}</style>"; // 配置段间距
if (![html containsString:tag]) {
html = [tag stringByAppendingString:html];
}

NSDictionary *options = @{ DTDefaultFontSize:[NSNumber numberWithFloat:kFontSize(48)], // 字体大小
DTDefaultLineHeightMultiplier:[NSNumber numberWithFloat:1.5], //行间距
DTDefaultFontName:@"Helvetica"}; // 设置字体

NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding];
return [[NSAttributedString alloc] initWithHTMLData:data options:options documentAttributes:NULL];
}

获取渲染后的大小

NSAttributedString *attributedString = ...
DTCoreTextLayouter *layouter = [[DTCoreTextLayouter alloc] initWithAttributedString:attributedString];
CGRect maxRect = CGRectMake(10, 20, CGFLOAT_WIDTH_UNKNOWN, CGFLOAT_HEIGHT_UNKNOWN);
NSRange entireString = NSMakeRange(0, [attributedString length]);
DTCoreTextLayoutFrame *layoutFrame = [layouter layoutFrameWithRect:maxRect range:entireString];

CGSize sizeNeeded = [layoutFrame frame].size;

显示图片和Gif动图

如果是一个本地html文件,里面的图片也是本地展示是没问题,如果远程图片这个时候需要更多一些操作了。在解析到<img></img>标签的时候插入一个占位符,然后在代理方法里面解析获取,通过封装的DTLazyImageView图片来显示

NSString *html = @"<p>这是一段使用<strong>DTCoreText</strong>显示的富文本</p><p>这是第二段使用<span style=\"text-decoration:underline;\">DTCoreText</span>显示的富文本</p><p><img src=\"http://7xj2sw.com1.z0.glb.clouddn.com/QQ20171013-223759@2x.png\" _src=\"http://7xj2sw.com1.z0.glb.clouddn.com/QQ20171013-223759@2x.png\" style=\"width:200px;height:100px;\"/></p>";
- (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttachment:(DTTextAttachment *)attachment frame:(CGRect)frame
{

if([attachment isKindOfClass:[DTImageTextAttachment class]])
{
NSString *imageURL = [NSString stringWithFormat:@"%@", attachment.contentURL];

DTLazyImageView *imageView = [[DTLazyImageView alloc] initWithFrame:frame];
imageView.delegate = self;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = [(DTImageTextAttachment *)attachment image];
imageView.url = attachment.contentURL;

if ([imageURL containsString:@"gif"]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *gifData = [NSData dataWithContentsOfURL:attachment.contentURL];
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = DTAnimatedGIFFromData(gifData);
});
});
}

return imageView;

}
}

DTLazyImageView 异步下载这张图片,如果是一张gif图就不会动,需要下载完这个Gif的数据之后再做一次转换赋值。

如果上传的图片属性里面没有宽高属性style=\"width:200px;height:100px;,是显示不出来的。官方给的一个解决方案是在DTLazyImageView的代理方法 - (void)lazyImageView:(DTLazyImageView *)lazyImageView didChangeImageSize:(CGSize)size 里面获取它的originalSize是否为CGSizeZero,然后重新赋值,刷新布局。

- (void)lazyImageView:(DTLazyImageView *)lazyImageView didChangeImageSize:(CGSize)size {
NSURL *url = lazyImageView.url;
CGSize imageSize = size;

NSPredicate *pred = [NSPredicate predicateWithFormat:@"contentURL == %@", url];

BOOL didUpdate = NO;

// update all attachments that match this URL (possibly multiple images with same size)
for (DTTextAttachment *oneAttachment in [self.layoutFrame textAttachmentsWithPredicate:pred])
{
// update attachments that have no original size, that also sets the display size
if (CGSizeEqualToSize(oneAttachment.originalSize, CGSizeZero))
{
oneAttachment.originalSize = imageSize;

didUpdate = YES;
}
}

if (didUpdate)
{
// do it on next run loop because a layout pass might be going on
self.layouter = nil;
[self relayoutText];
}
}

但是展示View大小是有限制的,这个时候绘制的图片超出可视区域。目前想到一个解决方案是在- (void)lazyImageView:(DTLazyImageView *)lazyImageView didChangeImageSize:(CGSize)size 难道图片的size之后平凑出style的宽高属性。然后在重新初始化加载这个html字符串。

// 字符串中一些图片没有宽高,需要这里拿到宽高后再去reload
- (void)configNoSizeImageView:(NSString *)url size:(CGSize)size
{

NSString *imageInfo = [NSString stringWithFormat:@"_src=\"%@\"",url];
NSString *sizeString = [NSString stringWithFormat:@" style=\"width: %.fpx; height: %.0fpx;\"",size.width,size.height];
NSString *newImageInfo = [NSString stringWithFormat:@"_src=\"%@\"%@",url,sizeString];

if ([self.html containsString:imageInfo] && !self.isClose) {

NSString *newHtml = [self.html stringByReplacingOccurrencesOfString:imageInfo withString:newImageInfo];

// reload newHtml
}
}

至于为什么会有 src 和 _scr ?
使用了百度开源了一个富文本编辑器UEditorhttp://ueditor.baidu.com/website/onlinedemo.html 传图片的时候就会有多个 _scr,属性也都在这里面,所以就追加到_scr里面。

显示自定义控件

在展示自定义控件里展示输入框和数学公式

显示公式

[DTTextAttachment registerClass:[DTObjectTextAttachment class] forTagName:@"object"];
<p>
<span class="mathquill-embedded-latex" style="width: 26px; height: 41px;">\frac{2}{3}</span>是一个分数,1 小于<span class="mathquill-embedded-latex" style="width: 41px; height: 28px;">\sqrt{2}</span>。
</p>

我想自定定义控件的tagname为“mathquill-embedded-latex”,并不可以但是替换成“object”就可以,这里使用正则表达式替换mathquill-embedded-latex为object。然后拿到文本使用第三方库iosMathhttps://github.com/kostub/iosMath显示公式。

// 公式需要自定义标签,如果包含数学公式标签才处理,不需要每次正则表达式去匹配
- (NSString *)configMathlatex:(NSString *)html
{
NSString *string = html;

if ([string containsString:@"<span class=\"mathquill-embedded-latex\""]) {

NSError *error = nil;
NSString *pattern = @"<span class=\"mathquill-embedded-latex\" style=.*?>.*?</span>";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:NSRegularExpressionCaseInsensitive error:&error];
NSArray *array = [regex matchesInString:string options:NSMatchingReportProgress range:NSMakeRange(0, [string length])];

for (NSInteger i = array.count-1; i>=0; i--) {
NSTextCheckingResult *result = array[i];
NSString *latextext = [string substringWithRange:result.range]; // 需要变更的字符串
[string deleteCharactersInRange:result.range];
NSString *objectText = [latextext stringByReplacingOccurrencesOfString:@"span" withString:@"object"];
[string insertString:objectText atIndex:result.range.location];
}

}

return string;
}

显示输入框

[DTTextAttachment registerClass:[DTObjectTextAttachment class] forTagName:@"input"];
- (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttachment:(DTTextAttachment *)attachment frame:(CGRect)frame
{
if ([attachment isKindOfClass:[DTObjectTextAttachment class]]){
NSString *className = [attachment.attributes objectForKey:@"class"];
if ([className isEqualToString:@"mathquill-embedded-latex"]) { // 公式
attachment.verticalAlignment = DTTextAttachmentVerticalAlignmentCenter;
DTObjectTextAttachment *element = (DTObjectTextAttachment *)attachment;
NSString *content;
for (DTHTMLElement *oneChildElement in element.childNodes)
{
content = oneChildElement.text;
MTMathUILabel* label = [[MTMathUILabel alloc] init];
label.textAlignment = kMTTextAlignmentCenter;
label.frame = frame;
label.latex = content;
label.fontSize = 16;
return label;
}
}

NSString *type = [attachment.attributes objectForKey:@"type"];
if ([type isEqualToString:@"text"]) { // 文本输入框
UITextField *textField = self.textFieldArray[tfID];
textField.frame = frame;
textField.contentVerticalAlignment=UIControlContentVerticalAlignmentCenter;
textField.layer.cornerRadius = 6;
textField.delegate = self;
textField.leftViewMode = UITextFieldViewModeAlways;
textField.rightViewMode = UITextFieldViewModeAlways;
[textField setContentVerticalAlignment:UIControlContentVerticalAlignmentCenter];
return textField;
}

}

}

参考资料:

  1. https://docs.cocoanetics.com/DTCoreText
  2. http://blog.cnbang.net/tech/2630
  3. http://blog.cnbang.net/tech/2729
  4. http://www.jianshu.com/p/9f5ac4d47ef4

当你获得的越多,你就会越温柔,不会为一点失去就气得暴跳如雷,也不会因为别人的否定就变得愤怒又消极。好的成长,就该是让人变得越来越温柔。