欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-12-10 13:51:16
ios系统是一个十分注重用户体验的系统,在ios系统中,用户交互的方案也十分多,然而要在label中的某部分字体中添加交互行为确实不容易的,如果使用其他类似button的控...

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

一、扩展于rclabel的支持异步加载网络图片的富文本引擎的设计
在ios开发中,图文混排一直都是ui编程的一个核心点,也有许多优秀的第三方引擎,其中很有名的一套图文混排的框架叫做dtcoretext。但是在前些日的做的一个项目中,我并没有采用这套框架,原因有二,一是这套框架体积非常大,而项目的需求其实并不太高;二是要在这套框架中修改一些东西,难度也非常大,我最终采用的是一个叫做rclabel的第三方控件,经过一些简单的优化和完善,达到了项目的要求。
先来介绍一下我项目中的图文混排的需求:首先我从服务器中取到的数据是字符串,但是其中穿插图片的位置是一个html的图片标签,标签里的资源路径就是图片的请求地址。需要达到的要求是这些数据显示出来后,图片的位置要空出来,然后通过异步的网络请求获取图片的数据,再将图片插入文字中。
要自己实现一套这样的引擎确实会比较麻烦,幸运的是rclabel可以完美的帮我们解析带有html标签的数据,进行图文混排,我们先来看一下这个东西怎么用,下面是我封装的一个展示html数据的view:
@interface yhbasehtmlview()<yhrtlabelimagedelegate>
{
    //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 <nsobject>
@optional
/**
 *点击超链接后出发的代理方法 model中有链接地址和文字
 */
-(void)yhbaselinkinglabelclicklinking:(yhbaselinkinglabelmodel *)model;
/**
 *尺寸改变后出发的方法
 */
-(void)yhbaselinkinglabelsizechange:(cgsize)size;
@end
@interface yhbaselinkinglabel : yhbaseview
/**
 *文字数组 里面存放这文字对应的超链接对象
 */
@property(nonatomic,strong)nsarray<yhbaselinkinglabelmodel *> * textarray;
@property(nonatomic,weak)id<yhbaselinkinglabelprotocol>delegate;
/**
 *设置文字颜色
 */
@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()<yhbasehtmlviewprocotop>
@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<yhbaselinkinglabelmodel *> *)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<yhbaselinkinglabelmodel *> *)data{
    nsmutablestring * mutstr = [[nsmutablestring alloc]init];
    for (int i=0; i<data.count; i++) {
    //这个model中存放的是超链接部分的文字和对应的url
        yhbaselinkinglabelmodel * model = data[i];
        if (!model.linking) {
            [mutstr appendstring:model.text];
        }else {
            [mutstr appendstring:@"<a href="];
            [mutstr appendstring:model.linking];
            [mutstr appendstring:@">"];
            [mutstr appendstring:model.text];
            [mutstr appendstring:@"</a>"];
        }
    }
    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);
}
运行效果如下:

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

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