DTCoreText的集成与使用目录一、相关资源二、DTCoreText的集成三、DTCoreText的使用四、可能遇到的错误五、参考链接


DTCoreText是可以将HTML字符串转化为富文本使用的工具,既保证原生实现又能适应灵活的样式修改,而且相比于使用WebView显示内容在性能上也有很大优势。本篇就这一技术的使用进行总结。

目录

一、相关资源 二、DTCoreText的集成 三、DTCoreText的使用 四、可能遇到的错误 五、参考链接

一、相关资源

  1. DTCoreText源码下载
  2. DTCoreText官方文档
  3. DTCoreText集成文档
  4. 本文DTCoreText测试工程

温馨提示:文中代码量比较大,看起来可能比较费劲,所以先将本文的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,选择需要最低支持的系统版本

image.png

3.设置Scheme

将Run环境修改为Release模式,依次选择Product->Scheme->Edit Scheme

image.png

4.模拟器和真机分别编译

使用commadn+B执行代码编译,注意一定要选择正确的Target,我们既然是用于iOS,就应该选择DTCoreText (iOS)这个Target进行编译

真机环境编译.png

模拟器环境编译.png

5.打开编译缓存,获取静态库

在Xcode依次打开File->File Setting->DerivedData,然后点击之后的路径箭头可以找到DTCoreText的编译缓存文件夹

屏幕快照 2018-01-30 下午2.01.52.png

然后我们再依次打开Build->Products,将会看到适合模拟器和真机使用的两个静态库文件夹:

image.png

6.合并静态库

经过上述步骤,我分别得到了适合模拟器和真机使用的静态库,现在我们把它们合并。首先我们把Products文件拷贝到一个合适的位置,然后在控制台执行合并静态库的命令如下:

合并静态库.png

如图,我们使用lipo -create命令合并了适合模拟器和真机使用的可执行文件并放在了桌面上如下:

image.png

然后我们需要将两个文件分别对应拷贝到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;
}

注意:如果我们需要显示一些输入框之类的视图,也可以借助此方法。 最终效果图:

屏幕快照 2018-01-30 下午2.24.32.png

2.DTAttributedTextView

其实,DTAttributedTextView和DTAttributedLabel的用法几乎一样。从其源码上就能看出,DTAttributedLabel继承于DTAttributedTextContentView的,而DTAttributedTextView包含DTAttributedTextContentView属性。可以说DTAttributedTextView就是借助DTAttributedLabel实现的视图。

DTAttributedTextView是支持滑动显示的富文本视图,在使用时计算富文本的Frame也就没那么必要了,一般我们都会指定它的Frame。其在这里的用法就不累述了。

3.DTAttributedTextCell

如果我们需要在单元格上显示富文本,DTCoretText也为我们提供了特有的类来解决这个问题,那就是DTAttributedTextCell。通过这个单元格类,我们可以方便的设置富文本以及获取单元格高度。以下是使用DTAttributedTextCell显示富文本的核心代码:

3.1. 声明控制器内属性

@interface TestTableViewController ()<UITableViewDataSource,UITableViewDelegate,DTAttributedTextContentViewDelegate,DTLazyImageViewDelegate>
@property(nonatomic,strong)UITableView *tableView;
//普通单元格与富文本单元格
@property (nonatomic, copy) NSString *cellID_Normal;
@property (nonatomic, copy) NSString *cellID_DTCoreText;
//类似tabelView的缓冲池,用于存放图片大小
@property (nonatomic, strong) NSCache *imageSizeCache;
@property (nonatomic,strong)NSCache *cellCache;
//表视图数据源
@property (nonatomic, strong) NSArray  *dataSource;
//当前表视图是否在滑动
@property (nonatomic,assign)BOOL isScrolling;

@end

3.2.表视图代理方法返回单元格及其高度

//代理方法:返回单元格
- (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;
}

3.3.懒加载处理无宽高属性的图片

#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 reloadCurrentCell];
    }
}

3.4.创建富文本单元格的方法

#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 = [_cellCache objectForKey:key];
    if (!cell){
        cell = [[ZSDTCoreTextCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_cellID_DTCoreText];
        cell.attributedTextContextView.edgeInsets = UIEdgeInsetsMake(0, 15, 0, 15);
        [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];
        }
    }
    [cell.attributedTextContextView relayoutText];
    return cell;
}

3.5.处理表视图的刷新

如果当前表视图在滑动就不执行刷新,因为滑动时候会自动调用表视图的刷新方法

- (void)reloadCurrentCell{
    if (self.isScrolling) {
        return;
    }
    //如果当前表视图没有在滑动,就手动刷新当前在屏幕显示的单元格
    NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
    if(indexPaths){
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
        });
    }
}

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView{
    _isScrolling = NO;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    _isScrolling = YES;
}

3.6.set方法创建表视图与数据源

#pragma mark - set/get方法
- (UITableView *)tableView{
    if (_tableView == nil) {
        _tableView = [[UITableView  alloc] initWithFrame:CGRectMake(0, 0, ZSToolScreenWidth, ZSToolScreenHeight-64) style:UITableViewStylePlain];
        _tableView.dataSource = self;
        _tableView.delegate = self;
        [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:_cellID_Normal];
        //[_tableView registerClass:[ZSDTCoreTextCell class] forCellReuseIdentifier:_cellID_DTCoreText];
    }
    return _tableView;
}

- (NSArray *)dataSource{
    if(_dataSource == nil){
        NSMutableArray *noramDataArray = @[].mutableCopy;
        NSMutableArray *htmlDataArray = @[].mutableCopy;
        NSArray *images = @[@"https://i0.hdslb.com/bfs/archive/d5ad3cf95d32f3d2f2e3471a39120237200d84d8.jpg",
                           @"https://i0.hdslb.com/bfs/archive/71d2fed927d9351e759f408ca7d66c556c37a6b4.jpg",
                           @"https://i0.hdslb.com/bfs/archive/7f520b31b67cd5d89dd30b61b40711327bb00288.png",
                           @"https://i0.hdslb.com/bfs/archive/6edbe81bf74c106087ad139aca169d6e8d9d963b.jpg",
                           @"https://i0.hdslb.com/bfs/archive/805aa8f7ae722fcc277f425bb9927e29ec1d2468.jpg",
                           @"https://i0.hdslb.com/bfs/archive/a7c61d94c583363a970d2a2e339eea97f8f32317.jpg",
                           @"https://i0.hdslb.com/bfs/archive/b447ee1fd63b4cf6f4465a5621cc12898867d26b.jpg"];
        for(int i = 0;i<1000;i++){
            if(i <6){
                [noramDataArray addObject:[NSString stringWithFormat:@"测试普通单元格:%d",i]];
            }
            //这里提供的Html图片链接,没有宽高属性,代码中已经演示了如何处理
            int k = i % 6;
            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=%@ _src=%@></span>",i,images[k],images[k]];
            [htmlDataArray addObject:htmlString];
        }
        _dataSource = @[noramDataArray,htmlDataArray];
    }
    return _dataSource;
}

使用上述方法使用DTAttributedTextCell的效果图如下:

DTAttributedTextCell效果图

四、可能遇到的错误

原本上,在项目中使用只需将Release-iphones的DTCoreText.framework和DTFoundation.framework中拖入工程就可以使用了。然而我们编译工程却会遇到这样一个错误:"image not found” 解决方法如下图:

image.png

五、参考链接

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

优质内容筛选与推荐>>
1、将字符串s1中任何与字符串s2匹配的字符都删掉
2、hdu3652 数位dp
3、将应用图标添加到ubuntu dash中
4、开发小知识
5、nginx json日志


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号