DTCoreText是可以将HTML字符串转化为富文本使用的工具,既保证原生实现又能适应灵活的样式修改,而且相比于使用WebView显示内容在性能上也有很大优势。本篇就这一技术的使用进行总结。
目录
一、相关文档
二、DTCoreText的集成
三、DTCoreText的使用
四、可能遇到的错误
五、参考链接
一、相关文档
GitHub: https://github.com/Cocoanetics/DTCoreText
官方文档: https://docs.cocoanetics.com/DTCoreText/
集成文档:https://docs.cocoanetics.com/DTCoreText/docs/Setup%20Guide.html
本文Demo: https://github.com/DreamcoffeeZS/DTCoreTextDemo.git
先将本文的Demo链接放在这里,毕竟代码才是硬道理嘛!
二、DTCoreText的集成
在项目中使用DTCoreText需要它的两个静态库:DTCoreText.framework和DTFoundation,framework。但是从Github下载的文件却不能直接使用。起初我是直接从网上其他地方下载打包好的静态库来使用的,但这样会遗漏掉更新。所以还是总结了集成DTCoreText的具体步骤如下:
1.下载源码
创建一个文件夹DTCoreText,使用终端命令下载源码:
git clone --recursive [https://github.com/Cocoanetics/DTCoreText.git](https://github.com/Cocoanetics/DTCoreText.git) Externals/DTCoreText
2.设置编译配置
使用下载的工程可以根据需求修改编译配置运行得到适合不同平台的静态库。为了获取到适合iOS设备使用的SDK,我们选择工程Target为DTCoreText(iOS),并依次选择工程->TARGETS->DTCoreText(iOS)->Deployment Info,选择需要最低支持的系统版本
3.设置Scheme
将Run环境修改为Release模式,依次选择Product->Scheme->Edit Scheme:
4.模拟器和真机分别编译
使用commadn+B执行代码编译,注意一定要选择正确的Target,我们既然是用于iOS,就应该选择DTCoreText (iOS)这个Target进行编译
5.打开编译缓存,获取静态库
在Xcode依次打开File->File Setting->DerivedData,然后点击之后的路径箭头可以找到DTCoreText的编译缓存文件夹
然后我们再依次打开Build->Products,将会看到适合模拟器和真机使用的两个静态库文件夹:
6.合并静态库
经过上述步骤,我分别得到了适合模拟器和真机使用的静态库,现在我们把它们合并。首先我们把Products文件拷贝到一个合适的位置,然后在控制台执行合并静态库的命令如下:
如图,我们使用lipo -create命令合并了适合模拟器和真机使用的可执行文件并放在了桌面上如下:
然后我们需要将两个文件分别对应拷贝到Release-iphones文件夹中的DTCoreText.framework和DTFoundation.framework中。这样最终我们就得到想要的静态库。
三、DTCoreText的使用
在这之前我们准备一段测试用的Html如下:
_html = @"<span style=\"color:#333;font-size:15px;\"><strong>标题1</strong></span><br/><span align=\"left\" style=\"color:#333;font-size:15px;\">详细介绍详细介绍详细介绍详细介绍详细介绍详细介绍。</span><br/><img src=\"http://cn-qinqimaifang-uat.oss-cn-hangzhou.aliyuncs.com/img/specialist/upload/spcetiicwlz1v_54e2e00fa8a6faf66168571654dbfee2.jpg\" _src=\"http://cn-qinqimaifang-uat.oss-cn-hangzhou.aliyuncs.com/img/specialist/upload/spcetiicwlz1v_54e2e00fa8a6faf66168571654dbfee2.jpg\"><br/><br/><span style=\"color:#333;font-size:15px;\">百度:<a href=\"http://www.baidu.com.cn\">my testlink</a></span><br/><br/><span style=\"color:#333;font-size:15px;\">电话:<a href=\"tel:4008001234\">my phoneNum</a></span><br/><br/><span style=\"color:#333;font-size:15px;\">我邮箱:<a href=\"mailto:dreamcoffeezs@163.com\">my mail</a></span>";
关于DTCoreText,我们主要用到它的三个控件,分别是DTAttributedLabel,DTAttributedTextView和DTAttributedTextCell。下面对它们的使用进行具体说明:
1.DTAttributedLabel
顾名思义,我们也会把DTAttributedLabel当做一个UILabel来使用(虽然事实上DTAttributedLabel并不属于UILabel的子类)。这里针对DTAttributedLabel使用的三个问题进行介绍。
1.1显示基本文本
//1.创建DTAttributedLabel
_attributedLabel = [[DTAttributedLabel alloc] initWithFrame:CGRectZero];
_attributedLabel.delegate = self;
[self.view addSubview:self.attributedLabel];
//2.Html转化富文本
NSData *data = [_html dataUsingEncoding:NSUTF8StringEncoding];
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithHTMLData:data documentAttributes:NULL];
//3.计算Frame
//预设一个最大的Frame,限宽不限高,方便以后计算布局
_viewMaxRect = CGRectMake(15, 15, ZSToolScreenWidth - 15*2, CGFLOAT_HEIGHT_UNKNOWN);
DTCoreTextLayouter *layouter = [[DTCoreTextLayouter alloc] initWithAttributedString:attributedString];
NSRange entireStringRange = NSMakeRange(0, [attributedString length]);
DTCoreTextLayoutFrame *layoutFrame = [layouter layoutFrameWithRect:_viewMaxRect range:entireStringRange];
CGSize textSize = [layoutFrame frame].size;
self.attributedLabel.frame = CGRectMake(_viewMaxRect.origin.x, _viewMaxRect.origin.y, _viewMaxRect.size.width, textSize.height);
//4.设置富文本
self.attributedLabel.attributedString = [self getAttributedStringWithHtml:self.html];
1.2显示图片
既然是富文本,DTAttributedLabel也会显示图片。但是如果我们的Html字符串里图片链接没有包含大小,图片并不能正常显示。为了显示图片我们需要懒加载获取宽高再刷新显示,这就需要用到DTAttributedLabel的代理方法如下:
#pragma mark - Delegate:DTAttributedTextContentViewDelegate
//DTCoretText在解析Html的时候,如果遇到网络图片会插入一个占位符
//对于图片类型(DTImageTextAttachmentd)的占位符,我们使用DTLazyImageView来显示
- (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;
//处理gif图片
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;
}
return nil;
}
#pragma mark Delegate:DTLazyImageViewDelegate
//懒加载图片代理
- (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)
//更新所有匹配URL的占位符视图
for (DTTextAttachment *oneAttachment in [self.attributedLabel.layoutFrame textAttachmentsWithPredicate:pred]){
//update attachments that have no original size, that also sets the display size
//更新没有自带尺寸的占位视图(如网络图片)
if (CGSizeEqualToSize(oneAttachment.originalSize, CGSizeZero)){
//原始图片不带宽高,被识别后修改Html
oneAttachment.originalSize = imageSize;
//使用新拿到的图片尺寸更新HTML字符串
[self configNoSizeImageView:url.absoluteString size:imageSize];
didUpdate = YES;
}
}
if (didUpdate){
//重新显示富文本
CGSize textSize = [self getAttributedTextHeightHtml:self.html with_viewMaxRect:_viewMaxRect];
self.attributedLabel.frame = CGRectMake(_viewMaxRect.origin.x, _viewMaxRect.origin.y, _viewMaxRect.size.width, textSize.height);
self.attributedLabel.attributedString = [self getAttributedStringWithHtml:self.html];
self.attributedLabel.layouter = nil;
[self.attributedLabel relayoutText];
}
}
#pragma mark - private Methods
//使用得到的新图片尺寸,更新HtmlString字符串
- (void)configNoSizeImageView:(NSString *)url size:(CGSize)size{
//_viewMaxRect是预设的最大Frame
CGFloat imgSizeScale = size.height/size.width;
CGFloat widthPx = _viewMaxRect.size.width;
CGFloat heightPx = widthPx * imgSizeScale;
NSString *imageInfo = [NSString stringWithFormat:@"_src=\"%@\"",url];
NSString *sizeString = [NSString stringWithFormat:@"style=\"width:%.fpx; height:%.fpx;\"",widthPx,heightPx];
NSString *newImageInfo = [NSString stringWithFormat:@"_src=\"%@\"%@",url,sizeString];
if ([self.html containsString:imageInfo]) {
NSString *newHtml = [self.html stringByReplacingOccurrencesOfString:imageInfo withString:newImageInfo];
self.html = newHtml;
}
}
//使用HtmlString,和预设的Frame,计算富文本视图自适应后的高度
- (CGSize)getAttributedTextHeightHtml:(NSString *)htmlString with_viewMaxRect:(CGRect)_viewMaxRect{
//获取富文本
NSAttributedString *attributedString = [self getAttributedStringWithHtml:htmlString];
//获取布局器
DTCoreTextLayouter *layouter = [[DTCoreTextLayouter alloc] initWithAttributedString:attributedString];
NSRange entireString = NSMakeRange(0, [attributedString length]);
//获取Frame
DTCoreTextLayoutFrame *layoutFrame = [layouter layoutFrameWithRect:_viewMaxRect range:entireString];
//得到大小
CGSize sizeNeeded = [layoutFrame frame].size;
return sizeNeeded;
}
1.3响应事件
如果我们希望在DTAttributedLabel上操作点击事件,那么对应的我们也应该在Html中使用超链接A标签。这里我们需要用到如下的代理方法如下:
- (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView
viewForLink:(NSURL *)url
identifier:(NSString *)identifier
frame:(CGRect)frame{
//此方法需要我们在这里返回一个可以点击响应的控件如Button,然后就可以很方便的处理响应事件了,其中:
//url:Html中的超链接(可以根据需要设置http、tel、mailto等)
//identifier:属于该视图的唯一性标识
//frame:超链接字符串所在的位置,需要将自定义响应按钮设置为此Frame
return nil;
}
注意:如果我们需要显示一些输入框之类的视图,也可以借助此方法。
最终效果图:
2.DTAttributedTextView
其实,DTAttributedTextView和DTAttributedLabel的用法几乎一样。从其源码上就能看出,DTAttributedLabel继承于DTAttributedTextContentView的,而DTAttributedTextView包含DTAttributedTextContentView属性。可以说DTAttributedTextView就是借助DTAttributedLabel实现的视图。
DTAttributedTextView是支持滑动显示的富文本视图,在使用时计算富文本的Frame也就没那么必要了,一般我们都会指定它的Frame。其在这里的用法就不累述了。
3.DTAttributedTextCell
如果我们需要在单元格上显示富文本,DTCoretText也为我们提供了特有的类来解决这个问题,那就是DTAttributedTextCell。通过这个单元格类,我们可以方便的设置富文本以及获取单元格高度。以下是使用DTAttributedTextCell显示富文本的核心代码:
@interface TestTableViewController ()<UITableViewDataSource,UITableViewDelegate,DTAttributedTextContentViewDelegate,DTLazyImageViewDelegate>
@property(nonatomic,strong)UITableView *tableView;
//普通单元格与富文本单元格ID
@property (nonatomic, copy) NSString *cellID_Normal;
//@property (nonatomic, copy) NSString *cellID_DTCoreText;
//类似tabelView的缓冲池,用于存放图片大小
@property (nonatomic, strong) NSCache *imageSizeCache;
//表视图数据源
@property (nonatomic, strong) NSArray *dataSource;
@end
//返回单元格
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
switch (indexPath.section) {
case 0:{
//普通单元格
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:_cellID_Normal];
cell.textLabel.text = self.dataSource[indexPath.section][indexPath.row];
return cell;
break;
}
case 1:{
//自定义方法,创建富文本单元格
ZSDTCoreTextCell *dtCell = (ZSDTCoreTextCell *) [self tableView:tableView prepareCellForIndexPath:indexPath];
return dtCell;
break;
}
default:
break;
}
return nil;
}
//返回单元格高度
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(nonnull NSIndexPath *)indexPath{
switch (indexPath.section) {
case 0:{
return 50;
break;
}
case 1:{
ZSDTCoreTextCell *cell = (ZSDTCoreTextCell *)[self tableView:tableView prepareCellForIndexPath:indexPath];
return [cell requiredRowHeightInTableView:tableView];
break;
}
default:
break;
}
return 0;
}
#pragma mark - DTAttributedTextContentViewDelegate
//对于没有在Html标签里设置宽高的图片,在这里为其设置占位
- (UIView *)attributedTextContentView:(DTAttributedTextContentView *)attributedTextContentView viewForAttachment:(DTTextAttachment *)attachment frame:(CGRect)frame{
if([attachment isKindOfClass:[DTImageTextAttachment class]]){
//自定义的ZSDTLazyImageView继承于DTLazyImageView,增加了一个属性textContentView
//用于更新图片大小
ZSDTLazyImageView *imageView = [[ZSDTLazyImageView alloc] initWithFrame:frame];
imageView.delegate = self;
imageView.image = [(DTImageTextAttachment *)attachment image];
imageView.textContentView = attributedTextContentView;
imageView.url = attachment.contentURL;
return imageView;
}
return nil;
}
//对于无宽高懒加载得到的图片,缓存记录其大小,然后执行表视图更新
- (void)lazyImageView:(ZSDTLazyImageView *)lazyImageView didChangeImageSize:(CGSize)size{
BOOL needUpdate = NO;
NSURL *url = lazyImageView.url;
NSPredicate *pred = [NSPredicate predicateWithFormat:@"contentURL == %@", url];
/* update all attachments that matchin this URL (possibly multiple
images with same size)
*/
for (DTTextAttachment *oneAttachment in [lazyImageView.textContentView.layoutFrame textAttachmentsWithPredicate:pred]){
// update attachments that have no original size, that also sets the display size
if (CGSizeEqualToSize(oneAttachment.originalSize, CGSizeZero)){
oneAttachment.originalSize = size;
NSValue *sizeValue = [_imageSizeCache objectForKey:oneAttachment.contentURL];
if (!sizeValue) {
//将图片大小记录在缓存中,但是这种图片的原始尺寸可能很大,所以这里设置图片的最大宽
//并且计算高
CGFloat aspectRatio = size.height / size.width;
CGFloat width = ZSToolScreenWidth - 15*2;
CGFloat height = width * aspectRatio;
CGSize newSize = CGSizeMake(width, height);
[_imageSizeCache setObject:[NSValue valueWithCGSize:newSize]forKey:url];
}
needUpdate = YES;
}
}
if (needUpdate){
[self.tableView reloadData];
}
}
#pragma mark - private Methods
//创建富文本单元格,并更新单元格上的数据
//ZSDTCoreTextCell是自定义的继承于DTCoreTextCell的单元格
- (ZSDTCoreTextCell *)tableView:(UITableView *)tableView prepareCellForIndexPath:(NSIndexPath *)indexPath{
NSString *key = [NSString stringWithFormat:@"dtCoreTextCellKEY%ld-%ld", (long)indexPath.section, (long)indexPath.row];
ZSDTCoreTextCell *cell = [tableView dequeueReusableCellWithIdentifier:key];
if (!cell){
//cell = [tableView dequeueReusableCellWithIdentifier:key];
cell = [[ZSDTCoreTextCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:key];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
cell.accessoryType = UITableViewCellAccessoryNone;
cell.hasFixedRowHeight = NO;
cell.textDelegate = self;
cell.attributedTextContextView.shouldDrawImages = YES;
//记录在缓存中
//[_cellCache setObject:cell forKey:key];
}
//2.设置数据
//2.1为富文本单元格设置Html数据
[cell setHTMLString:self.dataSource[indexPath.section][indexPath.row]];
//2.2为每个占位图(图片)设置大小,并更新
for (DTTextAttachment *oneAttachment in cell.attributedTextContextView.layoutFrame.textAttachments) {
NSValue *sizeValue = [_imageSizeCache objectForKey:oneAttachment.contentURL];
if (sizeValue) {
cell.attributedTextContextView.layouter=nil;
oneAttachment.displaySize = [sizeValue CGSizeValue];
}
}
[cell.attributedTextContextView relayoutText];
return cell;
}
//创建数据源
- (NSArray *)dataSource{
if(_dataSource == nil){
NSMutableArray *noramDataArray = @[].mutableCopy;
NSMutableArray *htmlDataArray = @[].mutableCopy;
for(int i = 0;i<5;i++){
[noramDataArray addObject:[NSString stringWithFormat:@"测试普通单元格:%d",i]];
//这里提供的Html图片链接,没有宽高属性,代码中已经演示了如何处理
NSString *htmlString =[NSString stringWithFormat:@"<span style=\"color:#333;font-size:15px;\"><strong>收费标准%d:</strong></span><br/><span style=\"color:#333;font-size:15px;\">记住!砍价是由你自己先砍,砍不动时再由砍价师继续砍;由砍价师多砍下的部分,才按照下列标准收费:</span><br/><span style=\"color:#333;font-size:15px;\"><img src=\"[http://cn-qinqimaifang-uat.oss-cn-hangzhou.aliyuncs.com/img/specialist/upload/spcetiicwlz1v_54e2e00fa8a6faf66168571654dbfee2.jpg\](http://cn-qinqimaifang-uat.oss-cn-hangzhou.aliyuncs.com/img/specialist/upload/spcetiicwlz1v_54e2e00fa8a6faf66168571654dbfee2.jpg%5C)" _src=\"[http://cn-qinqimaifang-uat.oss-cn-hangzhou.aliyuncs.com/img/specialist/upload/spcetiicwlz1v_54e2e00fa8a6faf66168571654dbfee2.jpg\](http://cn-qinqimaifang-uat.oss-cn-hangzhou.aliyuncs.com/img/specialist/upload/spcetiicwlz1v_54e2e00fa8a6faf66168571654dbfee2.jpg%5C)"></span>",i];
[htmlDataArray addObject:htmlString];
}
_dataSource = @[noramDataArray,htmlDataArray];
}
return _dataSource;
}
使用上述方法使用DTAttributedTextCell的效果图如下:
四、可能遇到的错误
原本上,在项目中使用只需将Release-iphones的DTCoreText.framework和DTFoundation.framework中拖入工程就可以使用了。然而我们编译工程却会遇到这样一个错误:”image not found”
解决方法如下图:
五、参考链接
1.DTCoreText集成:
https://www.jianshu.com/p/b09d3fa1cd98
2.DTCoreText使用总结:
http://duxinfeng.com/2017/08/02/DTCoreText%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/
3.iOS富文本框架DTCoreText在UITableView上的使用
http://blog.csdn.net/lala2231/article/details/50780842
4.CoretText的优缺点
https://www.jianshu.com/p/6f8162537975