热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

iOSApp开发中扩展RCLabel组件进行基于HTML的文本布局

RCLabel组件基于CoreText框架,可以将HTML标记的文本内容转为富文本视图,这里我们就来解读如何在iOSApp开发中扩展RCLabel组件进行基于HTML的文本布局:

iOS系统是一个十分注重用户体验的系统,在iOS系统中,用户交互的方案也十分多,然而要在label中的某部分字体中添加交互行为确实不容易的,如果使用其他类似Button的控件来模拟,文字的排版又将是一个解决十分困难的问题。这个问题的由来是项目中的一个界面中有一些广告位标签,而这些广告位的标签却是嵌在文本中的,当用户点击文字标签的位置时,会跳转到响应的广告页。
CoreText框架和一些第三方库可以解决这个问题,但直接使用CoreText十分复杂,第三方库多注重于富文本的排版,对类似文字超链接的支持亦不是特别简洁,我们可以借助一些第三方的东西进行针对性更强,更易用的封装。
RCLabel是一个第三方的将html字符串进行文本布局的工具,代码十分轻巧,并且其是基于CoreText框架的,其原生性和扩展性十分强。

一、扩展于RCLabel的支持异步加载网络图片的富文本引擎的设计
在iOS开发中,图文混排一直都是UI编程的一个核心点,也有许多优秀的第三方引擎,其中很有名的一套图文混排的框架叫做DTCoreText。但是在前些日的做的一个项目中,我并没有采用这套框架,原因有二,一是这套框架体积非常大,而项目的需求其实并不太高;二是要在这套框架中修改一些东西,难度也非常大,我最终采用的是一个叫做RCLabel的第三方控件,经过一些简单的优化和完善,达到了项目的要求。
先来介绍一下我项目中的图文混排的需求:首先我从服务器中取到的数据是字符串,但是其中穿插图片的位置是一个HTML的图片标签,标签里的资源路径就是图片的请求地址。需要达到的要求是这些数据显示出来后,图片的位置要空出来,然后通过异步的网络请求获取图片的数据,再将图片插入文字中。
要自己实现一套这样的引擎确实会比较麻烦,幸运的是RCLabel可以完美的帮我们解析带有HTML标签的数据,进行图文混排,我们先来看一下这个东西怎么用,下面是我封装的一个展示html数据的view:
@interface YHBaseHtmlView()
{
    //RCLabel对象
    RCLabel * _rcLabel;
    //保存属性 用于异步加载完成后刷新
    RTLabelComponentsStructure * _origenComponent;
    //含html标签的数据字符串
    NSString * _srt;
}
@end
@implementation YHBaseHtmlView
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}
*/
- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
    //将rclabel初始化
        _rcLabel = [[RCLabel alloc]init];
        [self addSubview:_rcLabel];
    }
    return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        _rcLabel = [[RCLabel alloc]initWithFrame:frame];
        [self addSubview:_rcLabel];
    }
    return self;
}
-(void)reSetHtmlStr:(NSString *)htmlStr{
    _srt = htmlStr;
    //这个代理是我额外添加的 后面解释
    _rcLabel.imageDelegate=self;
    //设置frame
    _rcLabel.frame=CGRectMake(0, 0, self.frame.size.width, 0);
    //设置属性
    _origenCompOnent= [RCLabel extractTextStyle:htmlStr IsLocation:NO withRCLabel:_rcLabel];
    _rcLabel.compOnentsAndPlainText= _origenComponent;
   //获取排版后的size
    CGSize size = [_rcLabel optimumSize];
    //重新设置frame
    _rcLabel.frame=CGRectMake(0, 0, _rcLabel.frame.size.width, size.height);
    self.frame=CGRectMake(self.frame.origin.x, self.frame.origin.y, _rcLabel.frame.size.width, size.height);
}
//这是我额外添加的代理方法的实现
-(void)YHRTLabelImageSuccess:(RCLabel *)label{
    _origenCompOnent= [RCLabel extractTextStyle:_srt IsLocation:NO withRCLabel:_rcLabel];
    _rcLabel.compOnentsAndPlainText= _origenComponent;
   
    CGSize size = [_rcLabel optimumSize];
    _rcLabel.frame=CGRectMake(0, 0, _rcLabel.frame.size.width, size.height);
    self.frame=_rcLabel.frame;
    if ([self.delegate respondsToSelector:@selector(YHBaseHtmlView:SizeChanged:)]) {
        [self.delegate YHBaseHtmlView:self SizeChanged:self.frame.size];
    }
}
RCLabel的用法很简单,总结来说只有三步:
1.初始化并设置frame
2.通过带html标签的数据进行属性的初始化
3.将属性进行set设置并重设视图frame
RCLabel是很强大,并且代码很简练,但是其中处理图片的部分必须是本地的图片,即图片html标签中的路径必须是本地图片的名字,其内部是通过[UIImage ImageNamed:]这个方法进行图片的渲染的,所以要达到我们的需要,我们需要对其进行一些简单的扩展:
1、在属性设置方法中添加一个参数,来区分本地图片与网络图片:
//我在这个方法中添加了location这个bool值,实际上rclabel这个参数也是我添加的,是为了后面代理使用的
+ (RTLabelComponentsStructure*)extractTextStyle:(NSString*)dataimage IsLocation:(BOOL)location withRCLabel:(RCLabel *)rcLabel;
2、在实现方法中添加如下代码,因为原文件有1900多行,在其中弄清楚逻辑关系也确实费了我不小的力气,我这里只将我添加的代码贴过来
#warning 这里进行了兼容性处理
                if (location) {
                //本地图片的渲染
                    if (tempURL) {
                        UIImage  *tempImg = [UIImage imageNamed:tempURL];
                        component.img = tempImg;
                       
                    }
                }else{//这里做远程图片数据的处理
                //这里我进行了缓存的操作,这个缓存中心是我封装的框架中的另一套东西,这里可以不用在意
                    //先读缓存
                    NSData * ceche = [[YHBaseCecheCenter sharedTheSingletion] readCecheFile:tempURL fromPath:YHBaseCecheImage];
                    if (ceche) {
                        UIImage * tempImg = [UIImage imageWithData:ceche];
                        component.img=tempImg;
                    }else{
                    //在分线程中进行图片数据的获取
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                            if (tempURL) {
                                NSData * data = [YHBaseData getDataWithUrl:tempURL];
                                if (data) {
                                //获取完成后村缓存
                                    //做缓存
                                    [[YHBaseCecheCenter sharedTheSingletion]writeCecheFile:data withFileID:tempURL toPath:YHBaseCecheImage];
                                    //赋值 回调代理
                                    UIImage * tempImg = [UIImage imageWithData:data];
                                    component.img=tempImg;
                                    //这里代理是我添加的,当图片下载完成后 通知视图重新排版
                                    if ([[rcLabel imageDelegate]respondsToSelector:@selector(YHRTLabelImageSuccess:)]) {
                                        //在主线程中执行回调
                                        //这个地方要在主线程中执行,否则刷新会有延时
                                        dispatch_async(dispatch_get_main_queue(), ^{
                                             [[rcLabel imageDelegate] YHRTLabelImageSuccess:rcLabel];
                                        });
                           
                                    }
                                  
                                }
                               
                            };
                           
                        });
                    }                
                   
                }

二、视图类与模型类的设计
RCLabel的核心之处在于将HTML文本转换为富文本布局视图,因此我们可以将要显示的文本编程html字符串,将其可以进行用户交互的部分进行html超链接关联,RCLabel就检测到我们点击的区域进行响应逻辑的回调。设计类如下:
.h文件
//文本与超链接地址关联的model类 后面会说
@class YHBaseLinkingLabelModel;
@protocol YHBaseLinkingLabelProtocol
@optional
/**
 *点击超链接后出发的代理方法 model中有链接地址和文字
 */
-(void)YHBaseLinkingLabelClickLinking:(YHBaseLinkingLabelModel *)model;
/**
 *尺寸改变后出发的方法
 */
-(void)YHBaseLinkingLabelSizeChange:(CGSize)size;
@end
@interface YHBaseLinkingLabel : YHBaseView
/**
 *文字数组 里面存放这文字对应的超链接对象
 */
@property(nonatomic,strong)NSArray * textArray;
@property(nonatomic,weak)iddelegate;
/**
 *设置文字颜色
 */
@property(nonatomic,strong)UIColor * textColor;
/**
 *设置超链接文字颜色
 */
@property(nonatomic,strong)UIColor * linkColor;
/**
 *设置字体大小
 */
@property(nonatomic,assign)NSUInteger fontSize;
/**
 *设置超链接字体大小
 */
@property(nonatomic,assign)int linkingFontSize;
/**
 *设置是否显示下划线
 */
@property(nonatomic,assign)BOOL isShowUnderLine;
@end
.m文件
@interface YHBaseLinkingLabel()
@end
@implementation YHBaseLinkingLabel
{
    //以前博客中 封装的显示HTML字符串富文本的视图
    YHBaseHtmlView * _label;
}
/*
// 重载一些初始化方法
- (instancetype)init
{
    self = [super init];
    if (self) {
        _label = [[YHBaseHtmlView alloc]init];
        [self addSubview:_label];
        [_label mas_makeConstraints:^(MASConstraintMaker *make) {
            make.leading.equalTo(@0);
            make.trailing.equalTo(@0);
            make.top.equalTo(@0);
            make.bottom.equalTo(@0);
        }];
         _label.delegate=self;
    }
    return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        _label = [[YHBaseHtmlView alloc]init];
        [self addSubview:_label];
        [_label mas_makeConstraints:^(MASConstraintMaker *make) {
            make.leading.equalTo(@0);
            make.trailing.equalTo(@0);
            make.top.equalTo(@0);
            make.bottom.equalTo(@0);
        }];
         _label.delegate=self;
    }
    return self;
}
- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        _label = [[YHBaseHtmlView alloc]init];
        [self addSubview:_label];
        [_label mas_makeConstraints:^(MASConstraintMaker *make) {
            make.leading.equalTo(@0);
            make.trailing.equalTo(@0);
            make.top.equalTo(@0);
            make.bottom.equalTo(@0);
        }];
        _label.delegate=self;
    }
    return self;
}
//设置文本数组
-(void)setTextArray:(NSArray *)textArray{
    _textArray = textArray;
    //进行html转换
    NSString * htmlString = [self transLinkingDataToHtmlStr:textArray];
    //进行布局
    [_label reSetHtmlStr:htmlString];
   
}
-(void)setTextColor:(UIColor *)textColor{
    _textColor = textColor;
    _label.fOntColor= textColor;
}
-(void)setLinkColor:(UIColor *)linkColor{
    _linkColor = linkColor;
    _label.linkingColor = linkColor;
}
-(void)setFontSize:(NSUInteger)fontSize{
    _fOntSize= fontSize;
    [_label setFontSize:(int)fontSize];
}
-(void)setLinkingFontSize:(int)linkingFontSize{
    _linkingFOntSize= linkingFontSize;
    [_label setLinkingSize:linkingFontSize];
}
-(void)setIsShowUnderLine:(BOOL)isShowUnderLine{
    _isShowUnderLine = isShowUnderLine;
    [_label setShowUnderLine:isShowUnderLine];
}
-(NSString *)transLinkingDataToHtmlStr:(NSArray *)data{
    NSMutableString * mutStr = [[NSMutableString alloc]init];
    for (int i=0; i     //这个model中存放的是超链接部分的文字和对应的url
        YHBaseLinkingLabelModel * model = data[i];
        if (!model.linking) {
            [mutStr appendString:model.text];
        }else {
            [mutStr appendString:@""];
            [mutStr appendString:model.text];
            [mutStr appendString:@"
"];
        }
    }
    return mutStr;
}
#pragma mark delegate
//点击的回调
-(void)YHBaseHtmlView:(YHBaseHtmlView *)htmlView ClickLink:(NSString *)url{
    for (YHBaseLinkingLabelModel * model in _textArray) {
        if ([model.linking isEqualToString:url]) {
            if ([self.delegate respondsToSelector:@selector(YHBaseLinkingLabelClickLinking:)]) {
                [self.delegate YHBaseLinkingLabelClickLinking:model];
                return;
            }
        }
    }
}
//布局尺寸改变的回调
-(void)YHBaseHtmlView:(YHBaseHtmlView *)htmlView SizeChanged:(CGSize)size{
    if ([self.delegate respondsToSelector:@selector(YHBaseLinkingLabelSizeChange:)]) {
        [self.delegate YHBaseLinkingLabelSizeChange:size];
    }
}
@end
上面我们有用到一个YHBaseLinkingLabelModel类,这个类进行了链接与字符的映射,设计如下:
@interface YHBaseLinkingLabelModel : YHBaseModel
/**
 *文字内容
 */
@property(nonatomic,strong)NSString * text;
/**
 *超链接地址 nil则为无
 */
@property(nonatomic,strong)NSString * linking;
@end
YHBaseHtmlView类是对RCLabel的一层封装,其中也对RCLabel进行了一些优化和改动,代码较多且在上篇博客中有介绍,这里不再多做解释了。
在ViewController中写如下代码进行使用:
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
   YHBaseLinkingLabel * label = [[YHBaseLinkingLabel alloc]initWithFrame:CGRectMake(100, 100, 200, 100)];
    NSMutableArray * array = [[NSMutableArray alloc]init];
    for (int i=0; i<6; i++) {
        YHBaseLinkingLabelModel * model = [[YHBaseLinkingLabelModel alloc]init];
        if (!(i%2)) {
            model.text =[NSString stringWithFormat:@"第%d个标签",i];
            model.linking = [NSString stringWithFormat:@"第%d个标签",i];
        }else{
            model.text = @",不能点得文字,";
        }
        [array addObject:model];
    }
    label.textColor = [UIColor blackColor];
    label.linkColor = [UIColor purpleColor];
    label.fOntSize= 15;
    label.linkingFOntSize= 17;
    label.isShowUnderLine=YES;
    label.delegate=self;
    label.textArray = array;
    [self.view addSubview:label];
  
}
-(void)YHBaseLinkingLabelClickLinking:(YHBaseLinkingLabelModel *)model{
    NSLog(@"%@",model.linking);
}
运行效果如下:

201671291423648.jpg (825×575)

效果不错,并且十分简单易用,对吧。


推荐阅读
  • 原理:dismiss再弹出,把dialog设为全局对象。if(dialog!null&&dialog.isShowing()&&!(Activity.)isFinishing()) ... [详细]
  • Java学习笔记之使用反射+泛型构建通用DAO
    本文介绍了使用反射和泛型构建通用DAO的方法,通过减少代码冗余度来提高开发效率。通过示例说明了如何使用反射和泛型来实现对不同表的相同操作,从而避免重复编写相似的代码。该方法可以在Java学习中起到较大的帮助作用。 ... [详细]
  • 本文内容为asp.net微信公众平台开发的目录汇总,包括数据库设计、多层架构框架搭建和入口实现、微信消息封装及反射赋值、关注事件、用户记录、回复文本消息、图文消息、服务搭建(接入)、自定义菜单等。同时提供了示例代码和相关的后台管理功能。内容涵盖了多个方面,适合综合运用。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 本文介绍了使用AJAX的POST请求实现数据修改功能的方法。通过ajax-post技术,可以实现在输入某个id后,通过ajax技术调用post.jsp修改具有该id记录的姓名的值。文章还提到了AJAX的概念和作用,以及使用async参数和open()方法的注意事项。同时强调了不推荐使用async=false的情况,并解释了JavaScript等待服务器响应的机制。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
author-avatar
k3as0n_701
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有